Lecture #25, Tuesday, April 19th
================================
 Our Types  The Picky Language
 Typing control
 Extending Picky
 Implementing Picky

# Our Types  The Picky Language
The first thing we need to do is to agree on what types are. Earlier,
we talked about two types: numbers and functions (ignore booleans or
anything else for now), we will use these two types for now.
> In general, this means that we are using the *Types are Sets* meaning
> for types, and specifically, we will be implmenting a type system
> known as a *HindleyMilner* system. This is *not* what Typed Racket
> is using. In fact, one of the main differences is that in our type
> system each binding has exactly one type, whereas in Typed Racket an
> identifier can have different types in different places in the code.
> An example of this is something that we've talked about earlier:
>
> (: foo : (U String Number) > Number)
> (define (foo x) ; \ these `x`s have a
> (if (number? x) ; / (U Number String) type
> (+ x 1) ; > this one is a Number
> (stringlength x))) ; > and this one is a String
A type system is presented as a collection of rules called "type
judgments", which describe how to determine the type of an expression.
Beside the types and the judgments, a type system specification needs a
(decidable) algorithm that can assign types to expressions.
Such a specification should have one rule for every kind of syntactic
construct, so when we get a program we can determine the precise type of
any expression. Also, these judgments are usually recursive since a
type judgment will almost always rely on the types of subexpressions
(if any).
For our restricted system, we have two rules (= judgments) that we can
easily specify:
n : Number (any numeral `n' is a number)
{fun {x} E} : Function
And what about an identifier? Well, it is clear that we need to keep
some form of an environment that will keep an account of types assigned
to identifiers (note: all of this is not at runtime). This environment
is used in all type judgments, and usually written as a capital Greek
Gamma character (in some places `G` is used to stick to ASCII texts).
The conventional way to write the two rules above is:
Γ ⊢ n : Number
Γ ⊢ {fun {x} E} : Function
The first one is read as "Gamma proves that `n` has the type `Number`".
Note that this is a syntactic environment, much like DEENVs that you
have seen in homework.
So, we can write a rule for identifiers that simply has the type
assigned by the environment:
Γ ⊢ x : Γ(x) ; "Γ(x)" is similar to a "lookup(x, Γ)"
We now need a rule for addition and a rule for application (note: we're
using a very limited subset of our old language, where arithmetic
operators are not function applications). Addition is easy: if we can
prove that both `a` and `b` are numbers in some environment Γ, then we
know that `{+ a b}` is a number in the same environment. We write this
as follows:
Γ ⊢ A : Number Γ ⊢ B : Number
———————————————————————————————
Γ ⊢ {+ A B} : Number
Now, what about application? We need to refer to some arbitrary type
now, and the common letter for that is a Greek lowercase tau:
Γ ⊢ F : Function Γ ⊢ V : τᵥ
—————————————————————————————
Γ ⊢ {call F V} : ???
that is  if we can prove that `f` is a function, and that `v` is a
value of some type `τₐ`, then ... ??? Well, we need to know more about
`f`: we need to know what type it consumes and what type it returns. So
a simple `function` is not enough  we need some sort of a function
type that specifies both input and output types. We will use the
notation that was seen throughout the semester and dump `function`. Now
we can write:
Γ ⊢ F : (τ₁ > τ₂) Γ ⊢ V : τ₁
——————————————————————————————
Γ ⊢ {call F V} : τ₂
which makes sense  if you take a function of type `τ₁>τ₂` and you
feed it what it expects, you get the obvious output type. But going
back to the language  where do we get these new arrow types from? We
will modify the language and require that every function specifies its
input and output type (and assume we have only one argument functions).
For example, we will write something like this for a function that is
the curried version of addition:
{fun {x : Number} : (Number > Number)
{fun {y : Number} : Number
{+ x y}}}
So: the revised syntax for the limited language that contains only
additions, applications and singleargument functions, and for fun 
go back to using the `call` keyword is. The syntax we get is:
::=

 { + }
 { fun { : } : }
 { call }
::= Number
 ( > )
and the typing rules are:
Γ ⊢ n : Number
Γ ⊢ {fun {x : τ₁} : τ₂ E} : (τ₁ > τ₂)
Γ ⊢ x : Γ(x)
Γ ⊢ A : Number Γ ⊢ B : Number
———————————————————————————————
Γ ⊢ {+ A B} : Number
Γ ⊢ F : (τ₁ > τ₂) Γ ⊢ V : τ₁
——————————————————————————————
Γ ⊢ {call F V} : τ₂
But we're still missing a big part  the current rule for a `fun`
expression is too weak, if we use it, we conclude that these
expressions:
{fun {x : Number} : (Number > Number)
3}
{fun {x : Number} : Number
{call x 2}}
are valid, as well concluding that this program:
{call {call {fun {x : Number} : (Number > Number)
3}
5}
7}
is valid, and should return a number. What's missing? We need to check
that the body part of the function is correct, so the rule for typing a
`fun` is no longer a simple one. Here is how we check the body instead
of blindly believing program annotations:
Γ[x:=τ₁] ⊢ E : τ₂ ; Γ[x:=τ₁] is similar to
—————————————————————————————————————— ; extend(Γ, x, τ₁)
Γ ⊢ {fun {x : τ₁} : τ₂ E} : (τ₁ > τ₂)
That is  we want to make sure that if `x` has type `τ₁`, then the
body expression `E` has type `τ₂`, and if we can prove this, then we can
trust these annotations.
There is an important relationship between this rule and the `call` rule
for application:
* In this rule we assume that the input will have the right type and
guarantee (via a proof) that the output will have the right type.
* In the application rule, we guarantee (by a proof) an input of the
right type and assume a result of the right type.
(Side note: Racket comes with a contract system that can identify type
errors dynamically, and assign blame to either the caller or the callee
 and these correspond to these two sides.)
Note that, as we said, `number` is really just a property of a certain
kind of values, we don't know exactly what numbers are actually used.
In the same way, the arrow function types don't tell us exactly what
function it is, for example, `(Number > Number)` can indicate a
function that adds three to its argument, subtracts seven, or multiplies
it by 7619. But it certainly contains much more than the previous naive
`function` type. (Consider also Typed Racket here: it goes much further
in expressing facts about code.)
For reference, here is the complete BNF and typing rules:
::=

 { + }
 { fun { : } : }
 { call }
::= Number
 ( > )
Γ ⊢ n : Number
Γ ⊢ x : Γ(x)
Γ ⊢ A : Number Γ ⊢ B : Number
———————————————————————————————
Γ ⊢ {+ A B} : Number
Γ[x:=τ₁] ⊢ E : τ₂
——————————————————————————————————————
Γ ⊢ {fun {x : τ₁} : τ₂ E} : (τ₁ > τ₂)
Γ ⊢ F : (τ₁ > τ₂) Γ ⊢ V : τ₁
——————————————————————————————
Γ ⊢ {call F V} : τ₂

Examples of using types (abbreviate `Number` as `Num`)  first, a
simple example:
{} ⊢ 5 : Num {} ⊢ 7 : Num
———————————————————————————
{} ⊢ 2 : Num {} ⊢ {+ 5 7} : Num
———————————————————————————————————————————
{} ⊢ {+ 2 {+ 5 7}} : Num
and a little more involved one:
[x:=Num] ⊢ x : Num [x:=Num] ⊢ 3 : Num
———————————————————————————————————————
[x:=Num] ⊢ {+ x 3} : Num
———————————————————————————————————————————————
{} ⊢ {fun {x : Num} : Num {+ x 3}} : Num > Num {} ⊢ 5 : Num
——————————————————————————————————————————————————————————————
{} ⊢ {call {fun {x : Num} : Num {+ x 3}} 5} : Num
Finally, try a buggy program like
{+ 3 {fun {x : Number} : Number x}}
and see where it is impossible to continue.
The main thing here is that to know that this is a type error, we have
to prove that there is no judgment for a certain type (in this case, no
way to prove that a `fun` expression has a `Num` type), which we
(humans) can only do by inspecting all of the rules. Because of this,
we need to also add an algorithm to our type system, one that we can
follow and determine when it gives up.

# Typing control
> [PLAI §26]
We will now extend our typed Picky language to have a conditional
expression, and predicates. First, we extend the BNF with a predicate
expression, and we also need a type for the results:
::=

 { + }
 { < }
 { fun { : } : }
 { call }
 { if }
::= Number
 Boolean
 ( > )
Initially, we use the same rules, and add the obvious type for the
predicate:
Γ ⊢ A : Number Γ ⊢ B : Number
———————————————————————————————
Γ ⊢ {< A B} : Boolean
And what should the rule for `if` look like? Well, to make sure that
the condition is a boolean, it should be something of this form:
Γ ⊢ C : Boolean Γ ⊢ T : ??? Γ ⊢ E : ???
———————————————————————————————————————————
Γ ⊢ {if C T E} : ???
What would be the types of `t` and `e`? A natural choice would be to
let the programmer use any two types:
Γ ⊢ C : Boolean Γ ⊢ T : τ₁ Γ ⊢ E : τ₂
—————————————————————————————————————————
Γ ⊢ {if C T E} : ???
But what would the return type be? This is still a problem. (BTW, some
kind of a union would be nice, but it has some strong implications that
we will not discuss.) In addition, we will have a problem detecting
possible errors like:
{+ 2 {if 3 {fun {x} x}}}
Since we know nothing about the condition, we can just as well be
conservative and force both arms to have the same type. The rule is
therefore:
Γ ⊢ C : Boolean Γ ⊢ T : τ Γ ⊢ E : τ
———————————————————————————————————————
Γ ⊢ {if C T E} : τ
 using the same letter indicates that we expect the types to be
identical, unlike the previous attempt. Consequentially, this type
system is fundamentally weaker than Typed Racket which we use in this
class.
Here is the complete language specification with this extension:
::=

 { + }
 { < }
 { fun { : } : }
 { call }
 { if }
::= Number
 Boolean
 ( > )
Γ ⊢ n : Number
Γ ⊢ x : Γ(x)
Γ ⊢ A : Number Γ ⊢ B : Number
———————————————————————————————
Γ ⊢ {+ A B} : Number
Γ ⊢ A : Number Γ ⊢ B : Number
———————————————————————————————
Γ ⊢ {< A B} : Boolean
Γ[x:=τ₁] ⊢ E : τ₂
——————————————————————————————————————
Γ ⊢ {fun {x : τ₁} : τ₂ E} : (τ₁ > τ₂)
Γ ⊢ F : (τ₁ > τ₂) Γ ⊢ V : τ₁
——————————————————————————————
Γ ⊢ {call F V} : τ₂
Γ ⊢ C : Boolean Γ ⊢ T : τ Γ ⊢ E : τ
———————————————————————————————————————
Γ ⊢ {if C T E} : τ

## Extending Picky
In general, we can extend this language in one of two ways. For
example, lets say that we want to add the `with` form. One way to add
it is what we did above  simply add it to the language, and write the
rule for it. In this case, we get:
Γ ⊢ V : τ₁ Γ[x:=τ₁] ⊢ E : τ₂
——————————————————————————————
Γ ⊢ {with {x : τ₁ V} E} : τ₂
Note how this rule encapsulates information about the scope of `with`.
Also note that we need to specify the types for the bound values.
Another way to achieve this extension is if we add `with` as a derived
rule. We know that when we see a
{with {x V} E}
expression, we can just translate it into
{call {fun {x} E} V}
So we could achieve this extension by using a rewrite rule to translate
all `with` expressions into `call`s of anonymous functions (eg, using
the `withstx` facility that we have seen recently). This could be done
formally: begin with the `with` form, translate to the `call` form, and
finally show the necessary goals to prove its type. The only thing to
be aware of is the need to translate the types too, and there is one
type that is missing from the typedwith version above  the output
type of the function. This is an indication that we don't really need
to specify function output types  we can just deduce them from the
code, provided that we know the input type to the function.
Indeed, if we do this on a general template for a `with` expression,
then we end up with the same goals that need to be proved as in the
above rule:
Γ[x:=τ₁] ⊢ E : τ₂
——————————————————————————————————————
Γ ⊢ {fun {x : τ₁} : τ₂ E} : (τ₁ > τ₂) Γ ⊢ V : τ₁
———————————————————————————————————————————————————————
Γ ⊢ {call {fun {x : τ₁} : τ₂ E} V} : τ₂
———————————————————————————————————————
Γ ⊢ {with {x : τ₁ V} E} : τ₂

Conclusion  we've seen type judgment rules, and using them in proof
trees. Note that in these trees there is a clear difference between
rules that have no preconditions  there are axioms that are always
true (eg, a numeral is always of type `num`).
The general way of proving a type seems similar to evaluation of an
expression, but there is a huge difference  *nothing* is really
getting evaluated. As an example, we always go into the body of a
function expression, which is done to get the function's type, and this
is later used anywhere this function is used  when you evaluate this:
{with {f {fun {x : Number} : Number x}}
{+ {call f 1} {call f 2}}}
you first create a closure which means that you don't touch the body of
the function, and later you use it twice. In contrast, when you prove
the type of this expression, you immediately go into the body of the
function which you have to do to prove that it has the expected
`Number>Number` type, and then you just use this type twice.
Finally, we have seen the importance of using the same type letters to
enforce types, and in the case of typing an `if` statement this had a
major role: specifying that the two arms can be any two types, or the
same type.

# Implementing Picky
The following is a simple implementation of the Picky language. It is
based on the environmentsbased Flang implementation. Note the two main
functions here  `typecheck` and `typecheck*`.
;;; <<>>
;; The Picky interpreter, verbose version
#lang pl
#
The grammar:
::=

 { + }
 {  }
 { = }
 { < }
 { fun { : } : }
 { call }
 { with { : } }
 { if }
::= Num  Number
 Bool  Boolean
 { > }
Evaluation rules:
eval(N,env) = N
eval(x,env) = lookup(x,env)
eval({+ E1 E2},env) = eval(E1,env) + eval(E2,env)
eval({ E1 E2},env) = eval(E1,env)  eval(E2,env)
eval({= E1 E2},env) = eval(E1,env) = eval(E2,env)
eval({< E1 E2},env) = eval(E1,env) < eval(E2,env)
eval({fun {x} E},env) = <{fun {x} E}, env>
eval({call E1 E2},env1)
= eval(Ef,extend(x,eval(E2,env1),env2))
if eval(E1,env1) = <{fun {x} Ef}, env2>
= error! otherwise  but this doesn't happen
eval({with {x E1} E2},env) = eval(E2,extend(x,eval(E1,env),env))
eval({if E1 E2 E3},env) = eval(E2,env) if eval(E1,env) is true
= eval(E3,env) otherwise
Type checking rules:
Γ ⊢ n : Number
Γ ⊢ x : Γ(x)
Γ ⊢ A : Number Γ ⊢ B : Number
———————————————————————————————
Γ ⊢ {+ A B} : Number
Γ ⊢ A : Number Γ ⊢ B : Number
———————————————————————————————
Γ ⊢ {< A B} : Boolean
Γ[x:=τ₁] ⊢ E : τ₂
——————————————————————————————————————
Γ ⊢ {fun {x : τ₁} : τ₂ E} : (τ₁ > τ₂)
Γ ⊢ F : (τ₁ > τ₂) Γ ⊢ V : τ₁
——————————————————————————————
Γ ⊢ {call F V} : τ₂
Γ ⊢ V : τ₁ Γ[x:=τ₁] ⊢ E : τ₂
——————————————————————————————
Γ ⊢ {with {x : τ₁ V} E} : τ₂
Γ ⊢ C : Boolean Γ ⊢ T : τ Γ ⊢ E : τ
———————————————————————————————————————
Γ ⊢ {if C T E} : τ
#
(definetype PICKY
[Num Number]
[Id Symbol]
[Add PICKY PICKY]
[Sub PICKY PICKY]
[Equal PICKY PICKY]
[Less PICKY PICKY]
[Fun Symbol TYPE PICKY TYPE] ; name, intype, body, outtype
[Call PICKY PICKY]
[With Symbol TYPE PICKY PICKY]
[If PICKY PICKY PICKY])
(definetype TYPE
[NumT]
[BoolT]
[FunT TYPE TYPE])
(: parsesexpr : Sexpr > PICKY)
;; parses sexpressions into PICKYs
(define (parsesexpr sexpr)
(match sexpr
[(number: n) (Num n)]
[(symbol: name) (Id name)]
[(list '+ lhs rhs) (Add (parsesexpr lhs) (parsesexpr rhs))]
[(list ' lhs rhs) (Sub (parsesexpr lhs) (parsesexpr rhs))]
[(list '= lhs rhs) (Equal (parsesexpr lhs) (parsesexpr rhs))]
[(list '< lhs rhs) (Less (parsesexpr lhs) (parsesexpr rhs))]
[(list 'call fun arg)
(Call (parsesexpr fun) (parsesexpr arg))]
[(list 'if c t e)
(If (parsesexpr c) (parsesexpr t) (parsesexpr e))]
[(cons 'fun more)
(match sexpr
[(list 'fun (list (symbol: name) ': itype) ': otype body)
(Fun name
(parsetypesexpr itype)
(parsesexpr body)
(parsetypesexpr otype))]
[else (error 'parsesexpr "bad `fun' syntax in ~s" sexpr)])]
[(cons 'with more)
(match sexpr
[(list 'with (list (symbol: name) ': type named) body)
(With name
(parsetypesexpr type)
(parsesexpr named)
(parsesexpr body))]
[else (error 'parsesexpr "bad `with' syntax in ~s" sexpr)])]
[else (error 'parsesexpr "bad expression syntax: ~s" sexpr)]))
(: parsetypesexpr : Sexpr > TYPE)
;; parses sexpressions into TYPEs
(define (parsetypesexpr sexpr)
(match sexpr
['Number (NumT)]
['Boolean (BoolT)]
;; allow shorter names too
['Num (NumT)]
['Bool (BoolT)]
[(list itype '> otype)
(FunT (parsetypesexpr itype) (parsetypesexpr otype))]
[else (error 'parsetypesexpr "bad type syntax in ~s" sexpr)]))
(: parse : String > PICKY)
;; parses a string containing a PICKY expression to a PICKY AST
(define (parse str)
(parsesexpr (string>sexpr str)))
;; Typechecker and related types and helpers
;; this is similar to ENV, but it holds type information for the
;; identifiers during typechecking; it is essentially "Γ"
(definetype TYPEENV
[EmptyTypeEnv]
[ExtendTypeEnv Symbol TYPE TYPEENV])
(: typelookup : Symbol TYPEENV > TYPE)
;; similar to `lookup' for type environments; note that the
;; error is phrased as a typecheck error, since this indicates
;; a failure at the type checking stage
(define (typelookup name typeenv)
(cases typeenv
[(EmptyTypeEnv) (error 'typecheck "no binding for ~s" name)]
[(ExtendTypeEnv id type restenv)
(if (eq? id name) type (typelookup name restenv))]))
(: typecheck : PICKY TYPE TYPEENV > Void)
;; Checks that the given expression has the specified type.
;; Used only for sideeffects (to throw a type error), so return
;; a void value.
(define (typecheck expr type typeenv)
(unless (equal? type (typecheck* expr typeenv))
(error 'typecheck "type error for ~s: expecting a ~s"
expr type)))
(: typecheck* : PICKY TYPEENV > TYPE)
;; Returns the type of the given expression (which also means
;; that it checks it). This is a helper for the real typechecker
;; that also checks a specific return type.
(define (typecheck* expr typeenv)
(: twonums : PICKY PICKY > Void)
(define (twonums e1 e2)
(typecheck e1 (NumT) typeenv)
(typecheck e2 (NumT) typeenv))
(cases expr
[(Num n) (NumT)]
[(Id name) (typelookup name typeenv)]
[(Add l r) (twonums l r) (NumT)]
[(Sub l r) (twonums l r) (NumT)]
[(Equal l r) (twonums l r) (BoolT)]
[(Less l r) (twonums l r) (BoolT)]
[(Fun boundid intype boundbody outtype)
(typecheck boundbody outtype
(ExtendTypeEnv boundid intype typeenv))
(FunT intype outtype)]
[(Call fun arg)
(cases (typecheck* fun typeenv)
[(FunT intype outtype)
(typecheck arg intype typeenv)
outtype]
[else (error 'typecheck "type error for ~s: expecting a fun"
expr)])]
[(With boundid itype namedexpr boundbody)
(typecheck namedexpr itype typeenv)
(typecheck* boundbody
(ExtendTypeEnv boundid itype typeenv))]
[(If condexpr thenexpr elseexpr)
(typecheck condexpr (BoolT) typeenv)
(let ([type (typecheck* thenexpr typeenv)])
(typecheck elseexpr type typeenv) ; enforce same type
type)]))
;; Evaluator and related types and helpers
(definetype ENV
[EmptyEnv]
[Extend Symbol VAL ENV])
(definetype VAL
[NumV Number]
[BoolV Boolean]
[FunV Symbol PICKY ENV])
(: lookup : Symbol ENV > VAL)
;; lookup a symbol in an environment, return its value or throw an
;; error if it isn't bound
(define (lookup name env)
(cases env
[(EmptyEnv) (error 'lookup "no binding for ~s" name)]
[(Extend id val restenv)
(if (eq? id name) val (lookup name restenv))]))
(: stripnumv : Symbol VAL > Number)
;; converts a VAL to a Racket number if possible, throws an error if
;; not using the given name for the error message
(define (stripnumv name val)
(cases val
[(NumV n) n]
;; this error will never be reached, see below for more
[else (error name "expected a number, got: ~s" val)]))
(: arithop : (Number Number > Number) VAL VAL > VAL)
;; gets a Racket numeric binary operator, and uses it within a NumV
;; wrapper
(define (arithop op val1 val2)
(NumV (op (stripnumv 'arithop val1)
(stripnumv 'arithop val2))))
(: boolop : (Number Number > Boolean) VAL VAL > VAL)
;; gets a Racket numeric binary predicate, and uses it
;; within a BoolV wrapper
(define (boolop op val1 val2)
(BoolV (op (stripnumv 'boolop val1)
(stripnumv 'boolop val2))))
(: eval : PICKY ENV > VAL)
;; evaluates PICKY expressions by reducing them to values
(define (eval expr env)
(cases expr
[(Num n) (NumV n)]
[(Id name) (lookup name env)]
[(Add l r) (arithop + (eval l env) (eval r env))]
[(Sub l r) (arithop  (eval l env) (eval r env))]
[(Equal l r) (boolop = (eval l env) (eval r env))]
[(Less l r) (boolop < (eval l env) (eval r env))]
[(Fun boundid intype boundbody outtype)
;; note that types are not used at runtime,
;; so they're not stored in the closure
(FunV boundid boundbody env)]
[(Call funexpr argexpr)
(let ([fval (eval funexpr env)])
(cases fval
[(FunV boundid boundbody fenv)
(eval boundbody
(Extend boundid (eval argexpr env) fenv))]
;; `cases' requires complete coverage of all variants, but
;; this `else' is never used since we typecheck programs
[else (error 'eval "`call' expects a function, got: ~s"
fval)]))]
[(With boundid type namedexpr boundbody)
(eval boundbody (Extend boundid (eval namedexpr env) env))]
[(If condexpr thenexpr elseexpr)
(let ([bval (eval condexpr env)])
(if (cases bval
[(BoolV b) b]
;; same as above: this case is never reached
[else (error 'eval "`if' expects a boolean, got: ~s"
bval)])
(eval thenexpr env)
(eval elseexpr env)))]))
(: run : String > Number)
;; evaluate a PICKY program contained in a string
(define (run str)
(let ([prog (parse str)])
(typecheck prog (NumT) (EmptyTypeEnv))
(let ([result (eval prog (EmptyEnv))])
(cases result
[(NumV n) n]
;; this error is never reached, since we make sure
;; that the program always evaluates to a number above
[else (error 'run "evaluation returned a nonnumber: ~s"
result)]))))
;; tests  including translations of the FLANG tests
(test (run "5") => 5)
(test (run "{< 1 2}") =error> "type error")
(test (run "{fun {x : Num} : Num {+ x 1}}") =error> "type error")
(test (run "{call {fun {x : Num} : Num {+ x 1}} 4}") => 5)
(test (run "{with {x : Num 3} {+ x 1}}") => 4)
(test (run "{with {identity : {Num > Num} {fun {x : Num} : Num x}}
{call identity 1}}")
=> 1)
(test (run "{with {add3 : {Num > Num}
{fun {x : Num} : Num {+ x 3}}}
{call add3 1}}")
=> 4)
(test (run "{with {add3 : {Num > Num}
{fun {x : Num} : Num {+ x 3}}}
{with {add1 : {Num > Num}
{fun {x : Num} : Num {+ x 1}}}
{with {x : Num 3}
{call add1 {call add3 x}}}}}")
=> 7)
(test (run "{with {identity : {{Num > Num} > {Num > Num}}
{fun {x : {Num > Num}} : {Num > Num} x}}
{with {foo : {Num > Num}
{fun {x : Num} : Num {+ x 1}}}
{call {call identity foo} 123}}}")
=> 124)
(test (run "{with {x : Num 3}
{with {f : {Num > Num}
{fun {y : Num} : Num {+ x y}}}
{with {x : Num 5}
{call f 4}}}}")
=> 7)
(test (run "{call {with {x : Num 3}
{fun {y : Num} : Num {+ x y}}}
4}")
=> 7)
(test (run "{with {f : {Num > Num}
{with {x : Num 3} {fun {y : Num} : Num {+ x y}}}}
{with {x : Num 100}
{call f 4}}}")
=> 7)
(test (run "{call {call {fun {x : {Num > {Num > Num}}}
: {Num > Num}
{call x 1}}
{fun {x : Num} : {Num > Num}
{fun {y : Num} : Num {+ x y}}}}
123}")
=> 124)
(test (run "{call {fun {x : Num} : Num {if {< x 2} {+ x 5} {+ x 6}}}
1}")
=> 6)
(test (run "{call {fun {x : Num} : Num {if {< x 2} {+ x 5} {+ x 6}}}
2}")
=> 8)
One thing that is very obvious when you look at the examples is that
this language is way too verbose to be practical  types are repeated
over and over again. If you look carefully at the typechecking
fragments for the two relevant expressions  `fun` and `with`  you
can see that we can actually get rid of almost all of the type
annotations.