From 2d5d47c89d5b9df3535029f04cf1205e47276451 Mon Sep 17 00:00:00 2001 From: JJ Date: Thu, 17 Aug 2023 16:44:19 -0700 Subject: docs: minor module changes, README example, pattern matching --- docs/TYPES.md | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) (limited to 'docs/TYPES.md') diff --git a/docs/TYPES.md b/docs/TYPES.md index 59cfd2f..b16c508 100644 --- a/docs/TYPES.md +++ b/docs/TYPES.md @@ -226,27 +226,55 @@ Unions are *tagged* type unions. They provide a high-level wrapper over an inner They are declared with `union[Variant: Type, ...]` and initialized with the name of a variant followed by its inner type constructor in brackets: `Square(side: 5)`. Tuple and struct types are special-cased to eliminate extraneous parentheses. ```puck +type Value = uint64 type Ident = string -type Expr = union - Literal: Term +type Expr = ref union + Literal: Value Variable: Ident - Abstraction: struct[param: Ident, func: Expr] - Application: struct[func, arg: Expr] + Abstraction: struct[param: Ident, body: Expr] + Application: struct[body, arg: Expr] Conditional: struct - if, then, else: Expr + cond, then_case, else_case: Expr ``` They take up as much space in memory as the largest variant, plus the size of the tag (typically one byte). #### pattern matching -todo... +Unions abstract over differing types. In order to *safely* be used, their inner types must be accessed via *pattern matching*: leaving no room for type confusion. Pattern matching in Puck relies on two syntactic constructs: the `match` statement, forcing qualification and handling of all possible types of a variable, and the `of` statement, querying type equality while simultaneously binding new identifiers to underspecified portions of variables. ```puck +import std/tables + +func eval(expr: Expr, context: var HashTable[Ident, Value]): Result[Value] + match expr + of Literal(value): + value + of Variable(ident): + context.get(ident) + of Application{body, arg}: + if body of Abstraction{param, body as inner_body}: + context.set(param, eval(arg)) + inner_body.eval(context) + else: + Error(InvalidExpr) + of Conditional{cond, then_case, else_case}: + if eval(cond, context): + then_case.eval(context) + else: + else_case.eval(context) + of _: + Error(InvalidExpr) ``` +The match statement takes exclusively a list of `of` sub-expressions, and checks for exhaustivity; but the `variable of Type(x)` syntax can be reused as a conditional, in `if` statements and elsewhere. + The `of` operator is similar to the `is` operator in that it queries type equality. However, unbound identifiers within `of` expressions are bound to appropriate values (if matched) and injected into the scope. +When matching unions with an inner product type (structs or tuples), external extraneous parenthesis are elided. + +todo: guards, either `where` or `if` + ### interfaces Interfaces can be thought of as analogous to Rust's traits, without explicit `impl` blocks and without need for the `derive` macro. -- cgit v1.2.3-70-g09d2