## Arithmetic ``` 1.2 * 4 + 1 # result: 5.8 ``` ## Few types to learn Most important ones: - `Num`: big-decimal - `List[T]`: (linked-) list of `T` - `Char`: unicode codepoint - `Unit`: nothing - `Cell[T]`: mutable cell of `T` Advanced (uncommon) types: - `Int8`, `Int16`, ... - `Uint8`, ... - `Flt32`, `Flt64` ## Functions ``` def msg(username: List[Char]) : List[Char] { "Hello, " ++ username ++ "!" # last expr is returned } def main() { print("hi") } main() ``` ## Simple, forward type-inference ``` def zero () : Flt32 { 3.1 # error: got Num, but expected F32 } ``` ## Bindings ``` let name = "Max" let passw = "1234" ``` ## Mutability only via `ref` ``` let value = Cell.from(1) print(Num.to_str(!value)) # !cell "observes" the cell value := 2 # `:=` mutates the cell print(Num.to_str(!value)) ``` ## no confusing function or operator overloading all operators: - `Num + Num` (has overloads for fixed width number types) - `Num - Num` (has overloads for fixed width number types) - `Num * Num` (has overloads for fixed width number types) - `Num / Num` (has overloads for fixed width number types) - `Num ^ Num`: raise to the power (has overloads for fixed width number types) - `List[t] ++ List[t]`: list concatenation - `value :: t` (explicitly specify type of value, useful for down-casting structs, or just code readability; does not perform casting) - `list[index]` - `!cell`: "observe" a mutable value - `cell := value`: mutate a mutable value ## non-nominal struct types ``` # `type` creates a non-distinct type alias type User = { name: List[Char] } type DbUser = { name: List[Char], pass: List[Char] } def example(u: User) : DbUser { u with pass: "1234" # has type { name: List[Char], pass: List[Char] } } def example2() : {name: List[Char], pass: List[Char]} { {name:"abc", pass:"123"} } def example3() : User { example2() # {name:.., pass:...} can automatically decay to {name:...} } ``` ## (tagged) union types ``` type Option[t] = 'Err # If no type specified after tag, defaults to Unit | 'Some t # the tags of unions are weakly attached to the types, but won't decay unless they have to def example(n: Num) : Num { let x = 'MyTag n # type of x is 'MyTag Num x # tag gets removed because target type is Num } def example2(n: Num) : Option[Num] { 'Some n } def example3-invalid() : Option[Num] { Unit # error: can't convert type `Unit` into type `'Err Unit | 'Some Num` # Either label the expression with 'Err, # or change the return type to Option[Unit], and label the expression with 'Some } def exampe4(): Option[Num] { 'Err Unit # type of this expression is: `'Err Unit` # enums can automatically cast, if all the cases from the source enum also exists in the target enum, # which they do here: `'Err Unit` is a case in `'Err Unit | Num` } def example5-error(): Option[Num] { let x = ( 'Err Unit ) :: Option[Unit] x # error: can't convert type `'Err Unit | 'Some Unit` into type `'Err Unit | 'Some Num` # The case `'Some Unit` does not exist in the target `'Err Unit | 'Some Num` } def example6-error(): Option[Unit] { let x = 'Error Unit x # in this case, the enum tag does not decay, like in `example`, # because we are casting to an enum # error: can't convert type `'Error Unit` into type `'Err Unit | 'Some Num`` # 1st possible solution: manually cast to just `Unit` (via `expr :: Unit`), so that it can convert to the second case of the target # 2nd possible solution: pattern match against the enum, to rename the tag from 'Error to 'Err } ``` ### pattern 1: labelled arguments ``` def [t] List.remove_prefix(prefix: List[t], list: 'from List[t]) ``` ## automatic return types ``` def add(a: Num, b: Num) -> _ { a + b } ``` ## templated generics ``` def [a,b] add(a: a, b: b) -> _ { a + b } add(1,2) add(1,"2") # error: in template expansion of add[Num,List[Char]]: No definition for `Num + List[Char]` ``` ## pattern matching ``` type Option[t] = 'None | 'Some t def [t] Match.`a++b`( # matching against this value value: List[t], # left hand side of operator l: List[t], # right hand side of operator r: MatchUtil.Var[List[t]] ) -> Option[{ r: List[t] }] { match List.remove_prefix(l, 'from value) { 'Some rem -> 'Some { r: rem } 'None -> 'None } } ``` then you can do: ``` type Token = 'Public Unit | 'Private Unit | 'Err Unit; def example(li: List[Char]) -> {t:Token,rem:List[Char]} { match li { "public" ++ rem -> {t: 'Public Unit, rem:rem} "private" ++ rem -> {t: 'Private Unit, rem:rem} _ -> {t: 'Err Unit, rem: li} } } ``` ## recursive data types ``` type List[t] = 'End | 'Cons {head:t, tail:List[t]} # now you might notice an issue with this # `type` defines non-distinct type alisases # so what is the type of this... # Introducing: type self references # the above example is the same as this: type List[t] = &a ('End | 'Cons {head:t, tail:a}) # example 2: # a List[List[t]] is just: &b ('End | 'Cons {head: &a ('End | 'Cons {head:t, tail:a}), tail: b}) ``` Infinitely sized types are not allowed: ``` &a {x:Num, y:a} ``` However, infinite types without size *are* allowed: ``` &a {x:a} ``` This is *not* allowed: ``` &a a ``` ## module system Each file is a "compilation unit" When compiling a compilation unit, the following inputs have to be provided: - any amount of files containing signatures of exported definitions. only definitions of the compilation unit that are in one of the signature files will get exported. - any amount of other files containing imported definitions Note that there is no practical difference between signature and source files. ### Example Export signature file `List.li`: ``` type List[t] = 'End | 'Cons {head:t, tail:List[t]} # not providing a function body makes it a function signature definition def [t] `a++b`(a: List[t], b: List[t]) -> List[t]; def [t] Match.`a++b`( value: List[t], l: List[t], r: MatchUtil.Var[List[t]] ) -> Option[{ r: List[t] }]; ``` Import signature file `Option.li`: ``` type Option[t] = 'None | 'Some t; ``` Compilation unit `List.lu`: ``` def [t] `a++b`(a: List[t], b: List[t]) -> List[t] { # ... } def [t] Match.`a++b`( value: List[t], l: List[t], r: MatchUtil.Var[List[t]] ) -> Option[{ r: List[t] }] { # ... } ``` ### Notes Each compilation unit gets compiled to implementation-specific bytecode. Templated functions can only be compiled partially during a compilation unit. This will impact compile speeds. Avoid templated functions wherever possible.