0

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")

val ff = fn : (int -> string -> bool) -> int -> string -> bool

I understand how to get this type but not so sure how to draw a parse tree to represent it.

For my homework we did do question like this which is way simpler : image for my other homework

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Liu Angie
  • 11
  • 1
  • 3
    Excuse the shameless plug, but take a look at this presentation of mine, it might help you. https://speakerdeck.com/igstan/a-type-inferencer-for-ml-in-200-lines-of-scala Also, two videos on it: https://www.youtube.com/watch?v=oPVTNxiMcSU https://www.youtube.com/watch?v=H7x4THVU4BQ – Ionuț G. Stan Jun 16 '17 at 06:35

1 Answers1

3

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.

Ionuț G. Stan
  • 176,118
  • 18
  • 189
  • 202