type Result(V,E) = Yay { val : V } | Nay { err : E }
root(x) => Result!(number, string):
if x >= 0 then Yay { "val": math.sqrt(x) }
else Nay { "err": "root of a negative number" }
int a = 2
string s = xs |> map(text) |> _[0]
int | string ios = 44
boolean b = "true"
Call stack:
> (repl:1:13)
┌─ repl
1 │ boolean b = "true"
│ ^^^^^^ This might be string
1 │ boolean b = "true"
└ ^^^^^^^^ This should be boolean
Error: A value that might be string cannot be used where boolean is expected.
Using types in SIL code: functions
foo(int a, boolean b) => if b then a else a+1
foo(int a, b) => if b then a else a+1
greet(Person p) => print("Hi " ~ p.name)
(int x) => x+1 // lambdas
(char a, char b) => [a,b,b,a]
// lambdas
x => int: x+1
(char a, char b) => string: [a,b,b,a]
//functions
foo(a, b) => int: if b then a else a+1
greet(Person p) => string: "Hi " ~ p.name
btw
> foo(a, b) => int: if b then a else a+1
> :st foo
(integer a, boolean b) -> integer
Using types in SIL code: matching on types
gimmeIntOrStr = match {
| int x => x*10
| string s => s.length
}
gimmeIntOrStr1 = match {
| (int x) => x*10
| (string s) => s.length
}
ntimes(str, cnt) => string:
match(str, cnt) {
| (string s, int n) when n > 0 => repeat(s, n) |> join
| (string _, int _) => "cnt must be positive"
}
Using types in SIL code: matching on types
Currently works only for basic types (int, number, boolean, char, and string) and names of registered D types.
> rem(a) => match(a) { | DivResult r => r.remainder }
> :st rem
(DivResult w) -> integer
Pattern matching happens at run time and uses the run time tags present in the value.
Using types in SIL code: casts
> json.parseJson("false") as boolean
:: boolean
false
> json.parseJson("false") as int
:: integer
Call stack:
> (repl:1:1)
Error: The value is described statically as integer but its runtime type is boolean.
> json.parseJson("33") as int
:: integer
33
> json.parseJson("33") as char
:: char
Call stack:
> (repl:1:1)
Error: The value is described statically as char but its runtime type is integer.
Using types in SIL code: type axioms
type Expr = int | Add { a : Expr, b : Expr }
f : (Expr) -> int
f(e) => match(e) {
| int x => x
| Add {"a":a, "b":b} => f(a) + f(b)
}
Here we annotate the type of f using an axiom to help SIL cope with a recursive type.
Coord {x:int, qq : char, y:int} <= Coord {x:int, y:int} Coord {x:int, qq : char, y:int} <= {x:int, y:int} Coord {x:int, y:int} <= {x:int, y:int}
but Coord {x:int, qq : char, y:int} is not a subtype of Point {x:int, y:int}
and {x:int, y:int} is not a subtype of Coord {x:int, y:int}.
If A2 <= A1 and R1 <= R2 then (A1) -> R1 <= (A2) -> R2.
Note the reversed order for argument types (they are contravariant).
Type checking & inference: how it works
toStr(x) => match(x) { | int n => text(n) | string s => s}
> :st toStr
(integer | string x) -> string
foo(o) => {
a = o.fred |> toStr
b = o.toto
a = a.length
in if b then a else o.fred + 2
}
Process one top level definition at a time:
Walk the AST computing types and collecting constraints.
Solve the constraints finding the type of the top level thing, report errors if constraints are inconsistent.
Type checking & inference: how it works
Walk the AST computing types and collecting constraints.
Constants like 2 or true give obvious types. Expressions and newly defined variables give fresh type variables (a.k.a. metavariables).
Introduce type vars #b, #toto, add constraints
#o >= {toto: #toto}
#b >= #toto
a = a.length
Introduce type vars #a2, #length, add constraints
#a >= {length: #length}
#a2 >= #length
Type checking & inference: how it works
in if b then a else o.fred + 2
introduce type var #ari for o.fred + 2 and #t for the result, add constraints
#b <= boolean
#fred <= integer | number
integer <= integer | number
#ari >= #fred | integer
#t >= #a2 | #ari
integer <= integer | number is obviously true, remove it
Type checking & inference: how it works
((integer | string x) -> string) <= ((#fred) -> #res)
is digested into smaller parts string <= #res #fred <= integer | string.
Combined with #fred <= integer | number
this gives #fred <= integer. We know #fred!
Combined with #res <= #a this gives #a >= string.
Which helps with #length - it should be integer.
#o >= {fred: #fred}
#o >= {toto: #toto}
combine into #o >= {fred: #fred, toto: #toto}.
Type checking & inference: how it works
#toto <= #b
#b <= boolean
gives #toto <= boolean. We know #toto!
So our function is (#o) -> #t where #o >= {fred: #fred, toto: #toto}
and #fred <= integer
and #t >= #a2 | #ari where #ari >= #fred | integer and #a2 >= #length which is int.
So #t simplifies to integer and we have our answer:
foo: ({fred: integer, toto: boolean}) -> integer
Type checking & inference: how it works
> :st toStr
(integer | string x) -> string
foo(o) => {
a = o.fred |> toStr
b = o.toto
a = a.length
in if b then a else o.fred + 2
}