commit c7aa5e19a9ffd6b3a6b8b8d69695c07b1b16b7a7 Author: Pat Garrity Date: Wed Jan 24 22:52:03 2024 -0600 First dump of madness. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..516c4a0 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +--- +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: fix-byte-order-marker + - id: mixed-line-ending + args: ['--fix=lf'] + description: Enforces using only 'LF' line endings. + - id: trailing-whitespace diff --git a/README.md b/README.md new file mode 100644 index 0000000..a93cbad --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# The Ava Programming Language + +This repository is currently a research project. The project is to explore ideas +in programming language design by specifying _Ava_. + +To start reading, please use the [Table of Contents](./table-of-contents.md) + +## What is Ava? + +Ava is a programming language research project that is in the design and +specification phase. There is no grammar, parser, compiler, or way to use any +Ava code. Ava is a way to get ideas out of my head, challenged, and further +explored. diff --git a/constants.md b/constants.md new file mode 100644 index 0000000..6193789 --- /dev/null +++ b/constants.md @@ -0,0 +1,30 @@ +# Constants + +_Constants_ are definitions which provide a name and explicit type for some +[value](values.md). The `const` keyword (definition type) is used to define a +constant. + +``` +const weak_pi: Float64 is 3.14 +const my_name: String is "Pat" +``` + +In general: + +``` +const : is +``` + +Since a constant is categorized as a [definition](general-syntax.md#definitions) +is uses the `is` keyword rather than the `:=` operator. + +## Restrictions + +Constants are subject to the following restrictions: + +- Constants MUST refer to a literal value. +- Constants MUST have an explicit type. +- Constants MAY refer to a record instance. +- Constants CANNOT require a type constructor. +- Constants CANNOT refer to functions. +- Constants CANNOT refer to expressions. diff --git a/definitions.md b/definitions.md new file mode 100644 index 0000000..e83008b --- /dev/null +++ b/definitions.md @@ -0,0 +1,29 @@ +# Definitions + +_Definitions_ are Ava constructs that may live at the top level within some +namespace. [Functions](functions.md) in particular may _also_ live within type +class (and instance) definitions. + +## Syntax + +All definitions adhere to the following syntax: + +``` +[export] is +``` + +Each definition type ultimately controls the description and body. All +definition names adhere to standard [Name](names.md) rules. + +## Supported Definition Types + +TODO: This whole document... can go away? Need inventory of keywords. + +- [`const`](constants.md) +- [`record`](records.md) +- [`type`](types.md) +- [`alias`](type-aliases.md) +- [`class`](type-classes.md) +- [`instance`](type-classes.md#instances) +- [`enum`](enumerations.md) +- [`fn`](functions.md) diff --git a/effects.md b/effects.md new file mode 100644 index 0000000..1222ff6 --- /dev/null +++ b/effects.md @@ -0,0 +1,8 @@ +# Effects + +## The Effect Type + +The type `IO[E, A]` represents an effect which will produce `A` when executed, +and may fail with an error of type `E`. There exists a type +`type Task[A] = IO[Nothing, A]` that represents effects that cannot fail with an +error. diff --git a/enumerations.md b/enumerations.md new file mode 100644 index 0000000..4311d19 --- /dev/null +++ b/enumerations.md @@ -0,0 +1,26 @@ +# Enumerations + +Enumerations are sum types. + +## Example: The Option Type + +``` +enum Option[A] is + record Some[A] is (value: A) + object None + +fn some[A]: (a: A) => Some[A] is a => Some(a) +``` + +## Example: The Either Type + +TODO: Describe import behavior! Does importing Either import Left/Right? + +``` +enum Either[L, R] is + record Left[L] is { value: L } + record Right[R] is { value: R } + +fn left[L]: (left: L) => Either[L, Nothing] is l => Left { l } +fn right[R]: (right: R) => Either[Nothing, R] is r => Right { r } +``` diff --git a/expressions.md b/expressions.md new file mode 100644 index 0000000..181fef4 --- /dev/null +++ b/expressions.md @@ -0,0 +1,45 @@ +# Expressions + +_Expressions_ are the basis of programming in Ava. An expression is some code +that will produce a _value_ when evaluated. + +## Syntax + +Most non-[Definition](definitions.md) code is considered an expression. +Expressions include: + +- Literals +- Variables +- Function Calls +- Control Expressions + +In general, using a Literal or a Variable for an expression wraps that value in +a `pure` function that returns the value when evaluated. + +## Control Expressions + +### If Then + +The `if/then` expression expects an expression that returns a _Boolean Value_. +That expression is evaluated. If it evaluates to `true`, the `then` expression +is evaluated. Otherwise the `else` expression is evaluated. + +``` +if then else +``` + +### Do Yield + +TODO: This is incomplete + +The `do/yield` expression allows for imperative composition of monadic +expressions. + +``` +do + x <- foo() + y <- bar() + _ <- baz() +yield + x + y +``` diff --git a/functions.md b/functions.md new file mode 100644 index 0000000..a4fd549 --- /dev/null +++ b/functions.md @@ -0,0 +1,34 @@ +# Functions + +TODO: Scala syntax? Haskell syntax? Implications? How do we specify names for +parameters? Do we want to do that? How do function arguments, tuples, and +records all relate to one another? Are they all just the same? How do we talk +about curried functions? Is that supported? Is there some other mechanism? + +TODO: This is all 100% up in the air right now. + +``` +fn example: () => Int32 is 42 +example +example() +``` + +``` +fn example: String => Int32 is str => length(str) +example "foo" +example("foo") +``` + +``` +fn example: { x: Int32, y: Int32 } => Int32 is (x, y) => x + y +example 1 2 +example(1, 2) +example { x: 1, y: 2 } +``` + +``` +fn map[F[*], A, B]: (F[A]) => (A => B) => F[B] is + (fa) (f) => fa f + +map(list)(x => x + 1) +``` diff --git a/general-syntax.md b/general-syntax.md new file mode 100644 index 0000000..151205a --- /dev/null +++ b/general-syntax.md @@ -0,0 +1,58 @@ +# General Syntax + +## Type Binding + +Some name may be bound to some type by using the `:` (colon) syntax. + +``` +name: Type +``` + +Type bindings are used for all such occurrences. This includes: + +- [Defining Variables](variables.md) +- [Defining Records](records.md) +- [Defining Functions](functions.md) + +## Value Binding + +Some name may be bound to some value by using the `:=` syntax. + +``` +name := value +name: Type := value +``` + +Value bindings are used for all such occurrences. This includes: + +- [Defining Variables](variables.md) +- [Instantiating Records](records.md) + +## Comments + +Comments are lines where the first non-whitespace characters are `--`. This was +selected for ease of typing paired with low visual noise. + +``` +-- this is a comment +-- this is another comment +let x := "foo" -- this will not compile, comments cannot be mixed with code +``` + +Ava does not support multi-line comments. + +## Code Documentation + +Code documentation is written using comments that both directly-precede certain +definitions and have 3 `-` characters: + +``` +--- This function does some foo as well as some bar. +--- @param x Documentation for the `x` parameter. +fn foo_bar: (x: String) => Int32 +``` + +- Code documentation respects Markdown. +- TODO: Link syntax. +- TODO: Supported definitions (example for each) +- TODO: Parameter documentation diff --git a/holes.md b/holes.md new file mode 100644 index 0000000..f0e935e --- /dev/null +++ b/holes.md @@ -0,0 +1,4 @@ +# Holes + +`???` is a hole that resolves to any type, used for development, cannot be used +at runtime at all (it blows up). diff --git a/memory-management.md b/memory-management.md new file mode 100644 index 0000000..7327319 --- /dev/null +++ b/memory-management.md @@ -0,0 +1 @@ +# Memory Management diff --git a/names.md b/names.md new file mode 100644 index 0000000..bfffa65 --- /dev/null +++ b/names.md @@ -0,0 +1,23 @@ +# Names + +_Names_ are user-defined strings that name defined items. All names (variable +names, function names, etc.) follow the same rules. + +Names are UTF-8 strings. The following values are _excluded_: + +- Any reserved keyword +- Any whitespace character +- `.` +- `:` +- `=` +- `"` or `'` +- `[` or `]` +- `{` or `}` +- `(` or `)` + +## Convention + +- Variables use `lower_snake_case` +- Functions use `lower_snake_case` +- Generic Types use `UpperCamelCase` but prefer single letters such as `A`. +- Data Types, aliases, etc. use `UpperCamelCase` diff --git a/namespaces.md b/namespaces.md new file mode 100644 index 0000000..5ec4d42 --- /dev/null +++ b/namespaces.md @@ -0,0 +1,76 @@ +# Namespaces + +_Namespaces_ are the primary organizational unit of Ava. Every Ava file _must_ +define a namespace. The namespace is ALWAYS the first line of the file. + +## Syntax + +Each namespace is an ordered, delimited, list of [names](names.md). Each name is +separated by the `.` character. + +``` +[.]* +``` + +- At least one name MUST be specified. +- CANNOT contain more than one `.` character in a row. +- CANNOT begin with `.` character. +- CANNOT end with `.` character. +- CANNOT use a [Reserved Name](#reserved-names). + +### Examples + +- `foo` +- `foo.bar.baz` +- `language.v0` +- `collections.v0` + +## Reserved Names + +Any namespaces provided with a particular Ava distribution are considered +_reserved_. This means that, for example, the following namespaces cannot be +used: + +- `language` +- `collections` + +## Language Namespace + +The `language` namespace is automatically imported into every Ava file. + +## Exports + +Each namespace MAY _export_ defintions using the `export` keyword. If some +definition is exported, it may be [imported](#imports) into another namespace. +Definitions are NOT exported by default -- it is an opt-in process. As noted in +the [Definitions](definitions.md) documentation, the `export` keyword may be +used at the beginning of _any_ definition. + +## Imports + +Imports immediately follow the namespace definition in each file. Each file may +have zero or more imports. Imports bring definitions from another namespace into +scope. + +### Syntax + +Each import is a single, fully-qualified name. Imported names MAY be mapped to +some alternative name. An import may refer to a specific namespace or it may +refer to some specific definition within a namespace. + +TODO: Account for type classes. How can we easily import instances? One option +is to NOT do this. Ever. Just resolve EVERY possible `instance` during +compilation and make them available within the scope of the program. Global. +Very strict one-instance-per-thing side-effect, but could be useful. + +``` +import [.] [as ] +``` + +### Examples + +``` +import collections.NonEmptyList as NEL +import collections.Queue +import collections +``` diff --git a/pattern-matching.md b/pattern-matching.md new file mode 100644 index 0000000..c0eddac --- /dev/null +++ b/pattern-matching.md @@ -0,0 +1,29 @@ +# Pattern Matching + +TODO: This needs just more description and examples and pressure testing. + +Ava supports pattern matching and value destructuring. + +## The Match Expression + +``` +match + case => + case => + ... +``` + +## The Default Case + +The _default case_, `_`, is used to catch all unmatched cases. This can be used +to only match a partial set of possible values: + +TODO: WIP gotta figure out functions. + +``` +fn example: String => Int32 is + str => match str + case "foo" => 1 + case "bar" => 2 + case _ => 3 +``` diff --git a/records.md b/records.md new file mode 100644 index 0000000..714bbae --- /dev/null +++ b/records.md @@ -0,0 +1,101 @@ +# Records + +Records may be defined. Each record contains one or more named fields. Note that +records are just [tuples](tuples.md) with named fields. In many ways, the two +can be interchanged. + +``` +record Foo is (x: String, y: Int32) +``` + +Record fields are both _ordered_ and _named_. + +- [Anonymous Records](#anonymous-records) +- [Generic Records](#generic-records) +- [Instantiating Records](#instantiating-records) +- [Copying Data](#copying-data) +- [Accessing Record Data](#accessing-record-data) +- [Tuple Interactions](#tuple-interactions) +- [Destructuring Records](#destructuring-records) + +## Anonymous Records + +Records do _not_ need to be named. + +``` +(x: String, y: String, z: String) +``` + +## Generic Records + +Records may have generically typed data, and accept a type constructor: + +``` +record Foo[A, B] is (x: A, y: B) +``` + +## Instantiating Records + +``` +record Foo is (x: String, y: Int32) + +let foo1 := Foo("foo", 1) +let foo2 := Foo(x := "foo", y := 1) +``` + +## Copying Data + +Copy syntax allows any record to be duplicated, with any fields explicitly +overridden by some value: + +``` +record Foo is (x: String, y: Int32) + +let foo := Foo("foo", 1) +let bar := copy(foo) +let baz := copy(foo, ("y": 2)) +``` + +## Accessing Record Data + +The `.` operator is used to access individual fields on a record. + +``` +record Foo is (x: String, y: Int32) + +let foo := Foo("foo", 1) +let bar := foo.x +``` + +Note that because records are just tuples, tuple syntax continues to work: + +``` +let baz: Int32 := foo._2 +``` + +## Tuple Interactions + +Use the following record definition for this section: + +``` +record Foo is (x: String, y: Int32) +``` + +Tuples can be assigned from records. + +``` +let foo := Foo("foo", 1) +let some_tuple: (String, Int32) := foo +``` + +Records can be assigned from tuples. + +``` +let some_tuple := ("foo", 1) +let foo: Foo := some_tuple +``` + +## Destructuring Records + +Records can be _destructured_ via [pattern matching](pattern-matching.md) +capabilities. This can take two possible forms. diff --git a/recursion.md b/recursion.md new file mode 100644 index 0000000..b46763a --- /dev/null +++ b/recursion.md @@ -0,0 +1,3 @@ +# Recursion + +TCO is supported. diff --git a/standard-library.md b/standard-library.md new file mode 100644 index 0000000..2e92ad6 --- /dev/null +++ b/standard-library.md @@ -0,0 +1,5 @@ +# Standard Library + +Things like supporting different standard functions, type class list, etc. + +What about collections? diff --git a/strings.md b/strings.md new file mode 100644 index 0000000..ed218ce --- /dev/null +++ b/strings.md @@ -0,0 +1 @@ +# Strings diff --git a/table-of-contents.md b/table-of-contents.md new file mode 100644 index 0000000..252caa7 --- /dev/null +++ b/table-of-contents.md @@ -0,0 +1,27 @@ +# Table of Contents + +- [Names](names.md) +- [Namespaces](namespaces.md) + - [Exports](namespaces.md#exports) + - [Imports](namespaces.md#imports) +- [Definitions](definitions.md) +- [General Syntax](general-syntax.md) +- [Expressions](expressions.md) +- [Values](values.md) +- [Constants](constants.md) +- [Variables](variables.md) +- [Effects](effects.md) +- [Records](records.md) +- [Types](types.md) +- [Type Aliases](type-aliases.md) +- [Type Classes](type-classes.md) +- [Enumerations](enumerations.md) +- [Type Unions](type-unions.md) +- [Functions](functions.md) +- [Strings](strings.md) +- [Tuples](tuples.md) +- [Memory Management](memory-management.md) +- [Holes](holes.md) +- [Pattern Matching](pattern-matching.md) +- [Recursion](recursion.md) +- [Standard Library](standard-library.md) diff --git a/tuples.md b/tuples.md new file mode 100644 index 0000000..8bab6a9 --- /dev/null +++ b/tuples.md @@ -0,0 +1,61 @@ +# Tuples + +Tuples are heterogeneous, fixed-size, collections of values. Tuples contain 0 to +`N` values, where `N` is unbounded. Tuples are similar to [Records](records.md), +but records have named parameters. + +## Syntax + +``` +let x := () +let y := ("foo") +let z := ("foo", 1) +let w := ("foo", 1, true, 3.14) +``` + +## Type of a Standard Tuple + +Consider the tuple `("foo", 1, true, 3.14)`. It has type +`(String, Int32, Boolean, Float64)`. + +## The Empty Tuple + +The type `()` has a single possible value, `()`. This is the empty tuple. It is +typically used as a token to indicate side-effects with no other useful output. + +## Accessing Tuple Members + +Each tuple member is _indexed_ and can be directly accessed via a property of +that index: + +``` +let w := ("foo", 1, true) +let x := w._1 +let y := w._2 +let z := w._3 +``` + +## Destructuring Tuples + +Tuples can be _destructured_ via [pattern matching](pattern-matching.md) +capabilities. This can take two possible forms. + +### Destructured Binding + +Tuples may be destructured at the point of binding. + +``` +let w := ("foo", 1, true) +let (x, y, z) := w +``` + +### Destructured Match Case + +``` +let w := ("foo", 1, true) + +let z := + match w + case ("foo", _, x) => x + case _ => false +``` diff --git a/type-aliases.md b/type-aliases.md new file mode 100644 index 0000000..d9dd816 --- /dev/null +++ b/type-aliases.md @@ -0,0 +1 @@ +# Type Aliases diff --git a/type-classes.md b/type-classes.md new file mode 100644 index 0000000..b15218f --- /dev/null +++ b/type-classes.md @@ -0,0 +1 @@ +# Type Classes diff --git a/type-unions.md b/type-unions.md new file mode 100644 index 0000000..60804e2 --- /dev/null +++ b/type-unions.md @@ -0,0 +1,8 @@ +# Type Unions + +TODO: Explore. What syntax is better? `|` seems better. + +``` +type Foo = String | Int32 +type Bar = Boolean or String or Int32 +``` diff --git a/types.md b/types.md new file mode 100644 index 0000000..f7c66cf --- /dev/null +++ b/types.md @@ -0,0 +1,69 @@ +# Types + +- [The Ava Type System](#the-ava-type-system) +- [The Nothing Type](#the-nothing-type) +- [Type Definitions](#type-definitions) + - [Type Constructors](#type-constructors) +- [Higher Kinded Types](#higher-kinded-types) + +## The Ava Type System + +Ava is based on a static type system with support for higher-kinded types. Ava +does not support inheritence. Ava _does_ support sum types +([Enumerations](enumerations.md)) and _does_ support +[type classes](type-classes.md). + +## The Nothing Type + +The special type, `Nothing`, cannot be instantiated. It can be used to describe +cases that cannot be expressed. For example, `Either[Nothing, String]` cannot +ever be a `Left`. `Nothing` can be used to satisfy any type requirement. For +example, `Either[Nothing, String]` satisfies `Either[Int32, String]`. + +## Type Definitions + +Types may directly, at the top level, be defined in terms of some +[type constructor](#type-constructors): + +``` +type TupleList[A, B] is List[(A, B)] +``` + +Or in general terms: + +``` +type is +``` + +### Type Constructors + +Type constructors essentially allow for types to be defined in terms of some set +of type inputs. Consider the prior example: + +``` +type TupleList[A, B] is List[(A, B)] +``` + +The `TupleList` type requires two type arguments, `A` and `B`, to be constructed +as a concrete type. For example, `TupleList[String, Int32]` is a concrete type. + +## Higher Kinded Types + +Type constructors in Ava may be higher order (e.g. not order 1) functions: + +``` +// Assume this exists +type List[A] is ??? +fn map[A, B]: (List[A], (A) => B) => List[B] + +type class Functor[F[*]] is + fn map[A, B]: (F[A])((A) => B) => F[B] + +instance Functor[List] is + fn map[A, B]: (List[A])((A) => B) => List[B] is + (list)(f) => map(list, f) +``` + +In this example, `Functor[F[*]]` is a type constructor which accepts a single +type argument. That type argument is a type constructor that accepts a single +concrete (order 0) type argument. diff --git a/values.md b/values.md new file mode 100644 index 0000000..89a9695 --- /dev/null +++ b/values.md @@ -0,0 +1 @@ +# Values diff --git a/variables.md b/variables.md new file mode 100644 index 0000000..bd062b0 --- /dev/null +++ b/variables.md @@ -0,0 +1,8 @@ +# Variables + +- [Immutable Values](#immutable-values) +- [Mutable Values](#mutable-values) + +## Variable Names + +Please refer to [Names](names.md).