Tons of work. Greater degree of consistency at this point. Main language is mostly reasonable.

This commit is contained in:
Pat Garrity 2024-01-28 22:01:12 -06:00
parent 218647484d
commit e9c6b3a39b
Signed by: pfm
GPG key ID: 5CA5D21BAB7F3A76
21 changed files with 735 additions and 120 deletions

30
arrays.md Normal file
View file

@ -0,0 +1,30 @@
# Arrays
Arrays are a standard Ava type. An array is a contiguous block of memory that
represents some grouping of a type.
```
let x: Array[Int32] := { 1, 2, 3 }
let zero: Array[Int32] := {}
let size: Int32 := size(x)
let arr: Array[Int32] := fill(512, 0)
```
## Type Class Support
- `Monad`
- `Show`
- `Eq`
## NonEmptyList
The type `NonEmptyList` is a list which cannot be empty:
```
given A
record NonEmptyList: (head: A, tail: List[A])
```
## Indexing
Lists cannot be accessed by index.

View file

@ -1,29 +0,0 @@
# 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] <definition type> <name> <description> <body>
```
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)

View file

@ -2,6 +2,22 @@
Enumerations are sum types.
## Syntax
An enumeration must have at least one type member.
```
enum <name>
-- records or objects...
end enum
```
## Objects
An _object_ is a special type of singleton. Much like the `()` type, each object
is an instance of itself (and the only instance of itself). Objects are used to
represent enumeration cases that do not have any inputs.
## Example: The Option Type
```
@ -14,14 +30,12 @@ end enum
given A
fn some: (a: A) => Some[A]
a => Some (a)
Some (a)
end fn
```
## Example: The Either Type
TODO: Describe import behavior! Does importing Either import Left/Right?
```
given L, R
enum Either
@ -30,12 +44,15 @@ enum Either
given R
record Right (value: R)
end enum
given L
fn left: (left: L) => Either<L, Nothing> := l => Left(l)
fn left: (left: L) => Either[L, Nothing]
Left(left)
end fn
given R
fn right: (right: R) => Either<Nothing, R> := Right(r)
fn right: (right: R) => Either[Nothing, R]
Right(right)
end fn
```

View file

@ -5,18 +5,15 @@ that will produce a _value_ when evaluated.
## Syntax
Most non-[Definition](definitions.md) code is considered an expression.
Expressions include:
Functions contain expressions. Expressions take the form of:
- Literals
- Variables
- Literal Values
- Variable Definitions
- Variable References
- Function Calls
- Control Expressions
- Control Structures
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
## Control Structures
### If Then
@ -28,18 +25,76 @@ is evaluated. Otherwise the `else` expression is evaluated.
if <boolean expr> then <expr> else <expr>
```
### Do Yield
TODO: This is incomplete
The `do/yield` expression allows for imperative composition of monadic
expressions.
### Match
```
do
x <- foo()
y <- bar()
_ <- baz()
yield
x + y
match <expr>
case <pattern> => <expr>
end match
```
### Do/Return
The `do/return` expression allows for imperative composition of monadic
expressions. It works with any types that are members of `Monad` or `BiMonad`.
#### Do/Return for Monad
Each step of the `do` is defined by an expression that produces a Monad. The
syntax desugars to `fmap` calls, where `return` is a `map` call.
```
fn foo: () => Option[Int32]
1
end fn
fn bar: () => Option[Int32]
2
end fn
fn baz: () => Option[Int32]
3
end fn
let example: Option[Int32] :=
do
x <- foo()
y <- bar()
_ <- baz()
return
x + y
end do
assert(example == Some(3))
```
#### Do/Return for BiMonad
The `BiMonad` is right biased, where the "left" side is assumed to represent
some error case. The typical use case for this behavior is the `IO[E, A]` type.
```
fn foo: () => IO[String, Int32]
sync(1)
end fn
fn bar: () => IO[String, Int32]
sync(2)
end fn
fn baz: () => IO[String, Int32]
sync(3)
end fn
let program: IO[String, Int32] :=
do
x <- foo()
y <- bar()
_ <- baz()
return
x + y
end do
program ->
sum => assert(sum == 3)
```

View file

@ -95,3 +95,16 @@ end fn
Note that this is equivalent to encoding a parameter as a synchronous effect and
evaluating it to value when needed.
## Function Composition
```
given A, B, C
fn compose: (f: (B) => C, g: (A) => B) => (A) => C
x => f(g(x))
end fn
```
## Variable Length Argument Lists
This feature is explicitly not supported by Ava. Lists should be used instead.

View file

@ -23,11 +23,6 @@ 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
@ -52,7 +47,58 @@ definitions and have 3 `-` characters:
fn foo_bar: (x: String) => Int32
```
- Code documentation respects Markdown.
- TODO: Link syntax.
- TODO: Supported definitions (example for each)
- TODO: Parameter documentation
## Block Definitions
All block definitions are closed by a companion `end` keyword that refers to the
initating keyword.
```
fn foo: () => ()
()
end fn
given A
class Bar
fn foo2: () => ()
end class
instance Bar[A]
fn foo2: () => ()
()
end fn
end instance
enum Color
object Red
object Green
object Blue
end enum
fn control_if: () => ()
if 1 + 1 > 2 then
()
else if 2 + 2 > 4 then
()
else
()
end if
end fn
fn control_do: () => Option[Int32]
do
x <- Some(1)
y <- Some(2)
return x + y
end fn
fn control_match: () => Int32
match "foo"
case "foo" => 1
case _ => 0
end match
end fn
infix plus: (left: Int32, right: Int32) => Int32
left + right
end infix
```

View file

@ -4,8 +4,9 @@ Example of defining `->` to do `map` for any functor:
```
given F[*] :: Functor, A, B
infix -> (fa: F[A], f: (A) => B) => F[B] :=
map (fa) (f)
infix ->: (fa: F[A], f: (A) => B) => F[B]
map (fa) (f)
end infix
```
```
@ -31,3 +32,12 @@ Infix operators for numeric operations are defined for tuples of size one.
```
(12 - 2) / 2
```
## Composition
```
given A, B, C
infix ∘: (f: B => C, g: A => B) => (A) => C
compose(f, g)
end infix
```

31
keywords.md Normal file
View file

@ -0,0 +1,31 @@
# Keywords
- `type`
- `class`
- `alias`
- `const`
- `enum`
- `record`
- `object`
- `let`
- `mut`
- `export`
- `import`
- `namespace`
- `infix`
- `fn`
- `end`
- `match`
- `case`
- `if`
- `then`
- `else`
- `do`
- `return`
- `given`
- `true`
- `false`
TODO:
- Concurrency Stuff?

32
lists.md Normal file
View file

@ -0,0 +1,32 @@
# Lists
Lists are a standard Ava type. Specifically, `List[A]` represents a linked list.
```
let x: List[Int32] := { 1, 2, 3 }
let y := prepend(x, 0)
let z := append(x, { 4, 5 })
let w: Option[Int32] := head(x)
let tail: List[Int32] := tail(x)
let sz: List[Int32] := size(x)
```
## Type Class Support
- `Monad`
- `Monoid`
- `Show`
- `Eq`
## NonEmptyList
The type `NonEmptyList` is a list which cannot be empty:
```
given A
record NonEmptyList: (head: A, tail: List[A])
```
## Indexing
Lists cannot be accessed by index.

View file

@ -5,12 +5,13 @@ names, function names, etc.) follow the same rules.
Names are UTF-8 strings. The following values are _excluded_:
- Any reserved keyword
- Any reserved keyword (in total)
- Any reserved symbol or operator (in total)
- Any whitespace character
- `.`
- `:`
- `=`
- `"` or `'`
- `"`
- `,`
- `\`
- `[` or `]`
- `(` or `)`
@ -20,3 +21,11 @@ Names are UTF-8 strings. The following values are _excluded_:
- Functions use `lower_snake_case`
- Generic Types use `UpperCamelCase` but prefer single letters such as `A`.
- Data Types, aliases, etc. use `UpperCamelCase`
## Name Conflicts
- Variables may have the same names as functions.
- Functions/infix may _not_ share names. No overloaded functions.
- Type classes may declare members with the same name, but if some type has an
instance of each conflicting type class, names cannot be known without
qualification.

View file

@ -9,14 +9,18 @@ Each namespace is an ordered, delimited, list of [names](names.md). Each name is
separated by the `.` character.
```
<name>[.<name>]*
namespace foo.bar.baz
```
```
[export] namespace <name>[.<name>]*
```
- 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).
- CANNOT start with a [Reserved Name](#reserved-names).
### Examples
@ -31,20 +35,49 @@ Any namespaces provided with a particular Ava distribution are considered
_reserved_. This means that, for example, the following namespaces cannot be
used:
- `language`
- `ava`
- `collections`
## Language Namespace
## Ava Namespace
The `language` namespace is automatically imported into every Ava file.
The `Ava` 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.
- Each file MAY _export_ definitions using the `export` keyword.
- If the `export` keyword is used at the beginning of the namespace declaration,
all definitions in the file are exported.
- Otherwise, definitions may be individually exported by prefixing them with the
`export` keyword.
### Export Syntax
```
export fn foo: () => Int32
10
end fn
export enum Color
object Red
object Yellow
object Blue
end enum
export namespace foo.bar.baz
export class Something
fn foo: () => Int32
end class
```
### Exporting Enumerations
For some enumeration, if the `enum` is exported, that means that all members of
the enumeration are also exported.
### Exporting Type Classes
If some type class is exported, all enclosed functions are also exported.
## Imports
@ -52,25 +85,67 @@ 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
### Import 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.
some alternative name using the `as` keyword. An import may refer to a specific
namespace or it may refer to some specific definition within a namespace.
```
import <namespace>[.<definition name>] [as <name>]
import <namespace>[.<definition name>|.*] [as <name>]
```
### Importing Enumerations
Importing an enumeration does NOT import each member of the enumeration. Using
some example enumeration:
```
export enum Color
object Red
object Yellow
object Blue
end enum
```
The import and usage might be:
```
import foo.bar.baz.Color
let x: Color := Color.Red
```
Direct imports or `*` imports can bring members into direct scope:
```
import foo.bar.baz.Color
import foo.bar.baz.Color.*
let x: Color := Red
```
### Importing Type Classes
When a type class is imported, its functions (both defined and
instance-delegated) are made available and in scope for all types that satisfy
that type class. _All `instance`s for the imported type class are resolved and
made available_. Type class instances cannot be imported directly.
- Only one `instance` may exist per `class` per type.
- `instance` are globally resolved.
### Splat Imports
Splat imports, aka `*`, pull _everything_ from the given path into scope. This
means that all exported members are imported. Notably, this does NOT lift
enumeration members into scope.
### Examples
```
import collections.NonEmptyList as NEL
import collections.Queue
import collections
import collections.*
import collections as colls
```

48
operators-symbols.md Normal file
View file

@ -0,0 +1,48 @@
# Operators & Symbols
## Language Level Symbols and Operators
These are reserved.
| Operator | Name |
| ------------------- | ---- |
| `:` | Bind Type to Name |
| `:=` | Bind Value to Name |
| `[...]` | Type Constructor |
| `(...)` | Tuple |
| `{...}` | List or Array |
| `--` | Comment |
| `---` | Docstring |
| `.` | Access Member or Numeric Literal. |
| `???` | Hole |
| `,` | Argument Separator |
| `=>` | Function Type Definition |
| `"..."` | String Literal |
| `*` | Type Constructor Argument |
| `\` | String Escape Sequence |
| `::` | Type Class Membership |
| `<-` | Do Binding |
| `_` | Anonymous Name |
| `@` | Object Metadata Reference |
| <code>&#124;</code> | Type Union |
## Standard Library Operators (Infix)
These are _not_ reserved names, but are worth mentioning.
| Operator | Name |
| ------------------------- | ---- |
| `=` | Equals (Boolean) |
| `!=` | Not Equals (Boolean) |
| `+` | Addition |
| `-` | Subtraction |
| `/` | Division |
| `<` | Division |
| `>` | Division |
| `<=` | Division |
| `>=` | Division |
| `∘` | Function Composition |
| `∧` | Logical AND |
| `` | Logical OR |
| `&&` | Logical AND |
| <code>&#124;&#124;</code> | Logical OR |

View file

@ -11,6 +11,7 @@ match <expr>
case <pattern> => <expr>
case <pattern> => <expr>
...
end match
```
## The Default Case
@ -21,9 +22,11 @@ to only match a partial set of possible values:
TODO: WIP gotta figure out functions.
```
fn example: String => Int32 is
str => match str
fn example: (str: String) => Int32
match str
case "foo" => 1
case "bar" => 2
case _ => 3
end match
end fn
```

View file

@ -31,7 +31,8 @@ Records do _not_ need to be named.
Records may have generically typed data, and accept a type constructor:
```
record Foo[A, B] (x: A, y: B)
given A, B
record Foo (x: A, y: B)
```
## Instantiating Records
@ -67,11 +68,7 @@ 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 syntax is _not_ supported on records.
## Tuple Interactions
@ -95,7 +92,28 @@ let some_tuple := ("foo", 1)
let foo: Foo := some_tuple
```
Records can be cast to tuples by extracting the tuple metadata from the record.
```
let foo := Foo("foo", 1)
let bar: (String, Int32) := @foo.tuple
```
TODO: Metdata is up in the air.
Records respect the tuple size metadata.
```
let size := @foo.size
```
Records provide field metadata.
```
let desc: RecordDescription := @foo.description
```
## Destructuring Records
Records can be _destructured_ via [pattern matching](pattern-matching.md)
capabilities. This can take two possible forms.
capabilities.

158
standard-type-classes.md Normal file
View file

@ -0,0 +1,158 @@
# Standard Type Classes
```
--- Associative binary operation.
--- **Associative**: `combine(x, combine(y, z)) = combine(combine(x, y), z)`
given A
class Semigroup
fn combine(x: A, y: A): A
end class
--- **Empty Identity**: `combine(x, empty()) = combine(empty(), x) = x`
given A :: Semigroup
class Monoid
fn empty() => A
end class
--- Type class for type constructors which can be mapped over.
---
--- ## Laws
---
--- **Composition**: `fa -> f -> g = fa -> (f ∘ g)`
--- **Identity**: `fa -> ((x) => x) = fa`
given F[*]
class Functor
--- Transform some wrapped data from one type to another, preserving the
--- wrapper.
---
--- @tparam A The type of input data.
--- @tparam B The type of output data.
--- @param fa The functor input.
--- @param f The function to transform data from `A` to `B`.
given [A, B]
fn map: (fa: F[A])(f: (A) => B) => F[B]
end class
-- Need Laws
given F[*] :: Functor
class Apply
given A, B
fn ap: (ff: F[(A) => B])(fa: F[A]) => F[B]
end class
-- Need Laws
given F[*] :: Apply
class Applicative
given A
fn pure: (value: A) => F[A]
fn unit: () => F[()]
pure(())
end fn
end class
--- Any Applicative satisfies Functor.
given F[*] :: Applicative
instance Functor[F]
given A, B
fn map: (fa: F[A])(f: (A) => B) => F[B]
ap(pure(f))(fa)
end fn
end instance
given F[*] :: Apply
class FlatMap
given A, B
fn fmap: (fa: F[A])(f: (A) => F[B]) => F[B]
given A
fn flatten: (ffa: F[F[A]]) => F[A]
fmap(ffa)((fa) => fa)
end fn
end class
given F[*] :: FlatMap, Applicative
class Monad
end class
--- Any Monad satisfies Functor.
--- Note for type class spec, must account for "override" precedence
given F[*] :: Applicative
instance Functor[F]
fn map: (fa: F[A])(f: (A) => B) => F[B]
fmap(fa)((a) => pure(f(a)))
end fn
end instance
given F[*, *]
class Bifunctor
def bimap[A, B, C, D](fab: F[A, B])(f: A => C, g: B => D): F[C, D]
given A, B, C, D
fn bimap(fab: F[A, B])(f: (A) => C, g: (B) => D) => F[C, D]
end class
given F[*] :: Functor
class CoFlatMap
given A, B
fn cofmap: (fa: F[A])(f: (F[A]) => B) => F[B]
given A
fn coflatten: (fa: F[A]) => F[F[A]]
cofmap(fa)((fa) => fa)
end fn
end class
given F[*] :: CoFlatMap
class CoMonad
given A
fn extract: (fa: F[A]) => A
end class
given A
class Show
fn show: (value: A) => String
end class
given A, B
class Eq
fn eq: (x: A, y: B) => Boolean
fn neq: (x: A, y: B) => Boolean
not(eq(x, y))
end fn
infix =: (x: A, y: B) => Boolean
eq(x, y)
end infix
infix !=: (x: A, y: B) => Boolean
neq(x, y)
end infix
end class
enum Comparison
object LessThan
object EqualTo
object GreaterThan
end enum
given A
class Compare
fn compare: (x: A, y: A) => Comparison
end class
given A
instance Eq[A, A]
fn eq: (x: A, y: A) => Boolean
match compare(x, y)
case EqualTo => true
case _ => false
end fn
end instance
given A
class HashCode
fn hash_code(data: A) => Int32
end class
```

20
standard-types.md Normal file
View file

@ -0,0 +1,20 @@
# Standard Types
`Byte`
`Boolean`
`Int8`
`UInt8`
`Int16`
`UInt16`
`Int32`
`UInt32`
`Int64`
`UInt64`
`Float32`
`Float64`
`BigInt`
`Decimal`
`String`
`(a, b, ...)` (Tuples)
`List[A]` (List of some type)
`Array[A]` (Array of some type)

View file

@ -1,31 +1,33 @@
# Table of Contents
- [Keywords](keywords.md)
- [Operators & Symbols](operators-symbols.md)
- [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)
- [Standard Types](standard-types.md)
- [Values](values.md)
- [Constants](constants.md)
- [Arrays](arrays.md)
- [Lists](lists.md)
- [Variables](variables.md)
- [Effects](effects.md)
- [Tuples](tuples.md)
- [Records](records.md)
- [Enumerations](enumerations.md)
- [Types](types.md)
- [Type Aliases](type-aliases.md)
- [Type Classes](type-classes.md)
- [Enumerations](enumerations.md)
- [Type Unions](type-unions.md)
- [Pattern Matching](pattern-matching.md)
- [Functions](functions.md)
- [Infix Operators](infix-operators.md)
- [Recursion](recursion.md)
- [Strings](strings.md)
- [Tuples](tuples.md)
- [Memory Management](memory-management.md)
- [Holes](holes.md)
- [Pattern Matching](pattern-matching.md)
- [Recursion](recursion.md)
- [Infix Operators](infix-operators.md)
- [Standard Library](standard-library.md)
- [Examples](examples.md)
- [Standard Types](standard-types.md)
- [Standard Type Classes](standard-type-classes.md)
- [Numeric Overflow](numeric-overflow.md)
- [Standard Library](standard-library.md)
- [Effects](effects.md)

View file

@ -11,16 +11,6 @@ let x := ()
let y := ("foo")
let z := ("foo", 1)
let w := ("foo", 1, true, 3.14)
let a: Int32 := unwrap(1)
```
Unwrapping a tuple of size one is just the identity function:
```
given A
fn unwrap: (a: A) => A
a
end fn
```
## Type of a Standard Tuple
@ -33,6 +23,10 @@ Consider the tuple `("foo", 1, true, 3.14)`. It has type
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.
## Maximum Tuple Size
The largest tuple is of size `uint32_max` (a constant value).
## Accessing Tuple Members
Each tuple member is _indexed_ and can be directly accessed via a property of
@ -68,4 +62,14 @@ let z :=
match w
case ("foo", _, x) => x
case _ => false
end match
```
## Metadata
`size`: Type `UInt32`, denotes the tuple size.
```
let x: (Int32, Int32, Int32) := (1, 2, 3)
let y: UInt32 := @x.size
```

View file

@ -1 +1,23 @@
# Type Aliases
An `alias` is an alternative name for some type. It is useful as either a local
rename or as a way to constrain types without overhead. An alias _cannot_ have a
type constructor.
The power of an alias comes from the fact that within the scope of the alias
definition, the alias and its bound type are treated as identical_.
```
alias Username := String
fn username: (candidate: String) => Either[InvalidUsername, Username]
-- do validation... and maybe it passes...
right(candidate)
end fn
instance Show[Username]
fn show: (value: Username) => String
value
end fn
end instance
end alias
```

View file

@ -1 +1,35 @@
# Values
Values consist of literals and the results of expressions.
## Literals for Standard Types
`Boolean`: `let x: Boolean := true`
`Int8`: `let x: Int8 := 0`
`UInt8`: `let x: UInt8 := 0`
`Int16`: `let x: Int16 := 0`
`UInt16`: `let x: UInt16 := 0`
`Int32`: `let x: Int32 := 0`
`UInt32`: `let x: UInt32 := 0`
`Int64`: `let x: Int64 := 0`
`UInt64`: `let x: UInt64 := 0`
`Float32`: `let x: Float32 := 0.0`
`Float64`: `let x: Float64 := 0.0`
`BigInt`: `let x: BigInt := 9999999999999999999999999999999`
`Decimal`: `let x: Decimal := 9999999999999999999999999999999.9999999999999999`
`String`: `let x: String := "foo"`
`List[A]`: `let x: List[Int32] := {1, 2, 3}`
`Array[A]`: `let x: Array[Int32] := {1, 2, 3}`
### Numeric Literal Defaults
- If no type is specified, any integer is assumed to be `Int32`.
- If no type is specified, any floating point value is assumed to be `Float64`.
## Tuple Literals
The type will be inferred to `(Int32, Int32, String)`
```
let x := (1, 2, "foo")
```

View file

@ -6,3 +6,20 @@
## Variable Names
Please refer to [Names](names.md).
## Immutable Values
Most variables are immutable, and cannot be reassigned.
```
let x := 10
```
## Mutable Values
Mutation is supported within the scope of a function.
```
mut x := 10
x := 20
```