Skip to content

Composite types

circular17 edited this page Oct 5, 2023 · 21 revisions

Composite types represent aggregated or organized data. They broadly encompass collections, associations, and tagged values.

Summary

A collection is a series of values while an association pairs keys with values. Below is an overview:

Collections

Name         Syntax      Ordered  Indexable  Deconstruct  Countable  Recursive  Unique  Lazy eval
-----------  ----------  -------  ---------  -----------  ---------  ---------  ------  ---------
Tuple        T*U           Yes       No          Yes         Yes        No        No       No
List         [T]           Yes       Yes         No          Yes        No        No       No
Linked list  T<>           Yes       No          No          No         Yes       No       No
Set          set{T}        No        No          No          Yes        No        Yes      No
Enumerator   T-enum        No        No          No          No         No        No       Yes

Associations

Name        Syntax        Ordered  Indexable  Deconstruct  Countable  Recursive  Unique  Lazy eval
----------  ------------  -------  ---------  -----------  ---------  ---------  ------  ---------
Dictionary  dict{T->U}      No        Yes         No          Yes        No        Yes    No
Record      {|a U, b V|}    No        No          Yes         No         No        Yes    Property

Note: in records, keys are identifiers with potential varied types. Record values can be accessed by member name, not by index.

Collections

Tuple

A tuple aggregates a fixed number of values without naming them, in a certain order:

  • (12, 47) has type int * int.
  • ("hello", 12.23) has type str * float.

You can deconstruct tuples:

  • let (x, y) = (3, 4) would assign the value 3 to x.

List

A list is a series of values of the same type, that has variable length and is ordered. It can be accessed by index:

  • let a = [1, 2, 3] defines a constant list of type [int].
  • a[1] = 2 is true.

Linked list

A linked list is similar to a list except it is built by joining the items with :: operator and ending with the empty linked list <>:

  • 12::34::67::<> is a linked list of type int<> with 3 elements.
  • 12::others is linked list with tail others, another linked list for elements after the head 12.

Set

A set is a collection of unique values of the same type. It is not ordered and elements can be added or deleted:

  • {1, 3, 2} is the same as {1, 2, 3} and is of type set{int}.
  • {1, 2} {+} {2, 3} will result to {1, 2, 3}.

Enumerator

An enumerator is a lazy evaluated list. It can only be traversed once. It is represented by two generics types T-enum and T-asyncEnum where T is the type for enumerated items.

Ranges are provided as a way of producing usual enumerators, like 0..<10 for the range from 0 to 10 excluded.

An enumerator can also be expressed with the enum keyword like a function that yields the values. For example:

enum n int "getNumbersSeq": int do // indicate the element type: int
    var i int = 0
    while i < n {
        yield i // provide a value
        i += 1
    }
end

The actual result type of this function is an enumerator int-enum. It is a record with a fetch function that return the next value. Here is how it would be implemented explicitly:

fun n int "getNumbersSeq": int-enum => {|
    _i int = 0
    state fun () "fetch" =>
        _i < n ? {
            let yieldValue = _i
            _i += 1
            return #ok yieldValue
        } else
            #done
|}

All collections are compatible with the enum/asyncEnum type because they can be enumerated.

Associations

Dictionary

A dictionary pairs unique keys with corresponding values.

  • var d = dict{5 -> "five", 7 -> "seven"} defines a variable dictionary of type dict{int -> str}.
  • d[5] := "cinco" changes the value associated with the key 5.
  • mut d del 5 removes the key 5.

Record

A record represents a collection of named values. While they bear similarities to objects in object-oriented programming with attributes such as properties and constructors, records in Append have their distinctive characteristics. Records can either be anonymous or have a designated type name.

Basic Usage: Create a point with an anonymous type.

  • let p1 = {| x: 4, y: 6 |} defines a point that assumes the type {| x int, y int |}, which can also be written as {| x/y int |}.

Named Records: Naming a record gives it a unique identity.

  • var r = new Rectangle {| x: 0, y: 0, width: 100, height: 100 |} defines a variable rectangle of the named type Rectangle. This name differentiates it from a similar anonymous record. The record type must be defined using the record keyword.

Deconstruction: Records also support deconstruction.

  • let {| x |} = {| x: 4; y: 6 |} extracts the specific value x from the record into a local constant x.
  • let {| x: horizontal |} = {| x: 4; y: 6 |} will renaming during deconstruction storing the value in a local constant horizontal.

Tagged value

Tagging a value clarifies its intent, especially useful for conveying success or errors:

  • #ok 42 is of type #ok int and expresses the success of a function, returning the value 42.
  • #done() or #done expresses the success of a function, but without return value.

These results can be handled with pattern matching.

You can nest tags for a more nuanced expression:

  • #ok #found 42 is equivalent to #ok (#found 42) where #ok is the enveloping tag.
Clone this wiki locally