So I am working on my practice final, there is problem ask me to draw a parse tree for this sml code:
fun ff f x y = if (f x y) then (f 3 y) else (f x \"zero\")
The simplest way I can think of is to slightly reformat your code and basically put each subexpression on its own line. Children are denoted by increased indentation.
Let's take your example:
fun ff f x y = if (f x y) then (f 3 y) else (f x "zero")
First, we need to do a little desugaring. The above is equivalent to this:
val ff =
fn f =>
fn x =>
fn y =>
if (f x y) then (f 3 y) else (f x "zero")
Now, let's put each subexpression on its own line (the parantheses are redundant in this particular example; everything works fine without them):
val ff =
fn f =>
fn x =>
fn y =>
if
f
x
y
then
f
3
y
else
f
x
"zero"
Finally, let's attach a type to each subexpression, but let's use type variables for types we don't know yet. Notice we're using the same type variables for the same occurrences. For example x
has type t3
everywhere.
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | t1 -> t2
fn x => | t3 -> t4
fn y => | t5 -> t6
if | t7 (* the type of the whole `if` expression *)
f | t1
x | t3
y | t5
then | t8 (* the type of the whole `then` part *)
f | t1
3 | int
y | t5
else | t9 (* the type of the whole `else` part *)
f | t1
x | t3
"zero" | string
You can then do some further simplifications by noticing that some type variables are equivalent or that they're used in certain contexts. For example, from these three calls to f
:
f x y
f 2 y
f x "zero"
we can gather that x
must be of type int
and y
must be of type string
. So, let's replace the type of x
, that is t3
, with int
, and the type of y
, that is t5
, with string
. We do this everywhere:
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | t1 -> t2
fn x => | int -> t4
fn y => | string -> t6
if | t7
f | t1
x | int
y | string
then | t8
f | t1
3 | int
y | string
else | t9
f | t1
x | int
"zero" | string
Also, f
is used as a function that is being passed an int
each time. The result is also used as a function and is being passed a string
. That means t1
, which is the type variable assigned to f
, must be of type int -> string -> r0
, for some r0
we don't know yet. Let's replace all occurrences of t1
with int -> string -> r0
.
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | (int -> string -> r0) -> t2
fn x => | int -> t4
fn y => | string -> t6
if | t7
f | int -> string -> r0
x | int
y | string
then | t8
f | int -> string -> r0
3 | int
y | string
else | t9
f | int -> string -> r0
x | int
"zero" | string
But notice something, r0
must be treated as a bool
, because the result of f x y
is used in the condition part of an if
expression, so let's replace r0
with bool
everywhere:
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | (int -> string -> bool) -> t2
fn x => | int -> t4
fn y => | string -> t6
if | t7
f | int -> string -> bool
x | int
y | string
then | t8
f | int -> string -> bool
3 | int
y | string
else | t9
f | int -> string -> bool
x | int
"zero" | string
Next, the type of the whole if
expression must be the same as the type of the then
part, which must be the same as the type of the else
part. So t7
, t8
and t9
must all be the same. Let's use t7
where we see t8
and t9
.
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | (int -> string -> bool) -> t2
fn x => | int -> t4
fn y => | string -> t6
if | t7
f | int -> string -> bool
x | int
y | string
then | t7
f | int -> string -> bool
3 | int
y | string
else | t7
f | int -> string -> bool
x | int
"zero" | string
Next, t7
must be of type bool
because the type of both f 3 y
and f x "zero"
is bool
:
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | (int -> string -> bool) -> t2
fn x => | int -> t4
fn y => | string -> t6
if | bool
f | int -> string -> bool
x | int
y | string
then | bool
f | int -> string -> bool
3 | int
y | string
else | bool
f | int -> string -> bool
x | int
"zero" | string
Next, t6
must be a bool
too, because we're returning the result of the if
expression:
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | (int -> string -> bool) -> t2
fn x => | int -> t4
fn y => | string -> bool
if | bool
f | int -> string -> bool
x | int
y | string
then | bool
f | int -> string -> bool
3 | int
y | string
else | bool
f | int -> string -> bool
x | int
"zero" | string
Next, t4
must be of type string -> bool
, because that's what the type of its body is:
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | (int -> string -> bool) -> t2
fn x => | int -> string -> bool
fn y => | string -> bool
if | bool
f | int -> string -> bool
x | int
y | string
then | bool
f | int -> string -> bool
3 | int
y | string
else | bool
f | int -> string -> bool
x | int
"zero" | string
What's t2
? It must be the type of the body, which is int -> string -> bool
:
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | t0
fn f => | (int -> string -> bool) -> int -> string -> bool
fn x => | int -> string -> bool
fn y => | string -> bool
if | bool
f | int -> string -> bool
x | int
y | string
then | bool
f | int -> string -> bool
3 | int
y | string
else | bool
f | int -> string -> bool
x | int
"zero" | string
Finally, the type t0
of ff
must be equal with the type of the value assigned to ff
. So:
Subexpression | Type
---------------------- | ------------------------------------------
val ff = | (int -> string -> bool) -> int -> string -> bool
fn f => | (int -> string -> bool) -> int -> string -> bool
fn x => | int -> string -> bool
fn y => | string -> bool
if | bool
f | int -> string -> bool
x | int
y | string
then | bool
f | int -> string -> bool
3 | int
y | string
else | bool
f | int -> string -> bool
x | int
"zero" | string
And that's your final result.