Exercises

Mandatory exercises

  • Exercise 0: checking out the generators of love letters and mathematical proofs
  • Exercise 7: finding the type of a given expression
  • Exercise 9: finding an expression of a given type
  • Exercise 10: a unit test for the successor function
  • Exercise 11: a unit test for the negation function
  • Exercise 13: a unit test for the twice-less-than comparison function
  • Exercise 15: a unit test for the function summing the first consecutive odd natural numbers

Exercise 0

In reference to the section about Using a grammar to generate grammatically correct sentences, check out Strachey’s generator of love letters and the generator of mathematical proofs. (Make sure to reload these web pages a few times.)

Exercise 18

Assuming the inference rule

NOTG |- e : bool
G |- not e : bool

and in a type environment where x has type int and y has type char, does the expression fun x -> not x have type bool -> bool or is it ill-typed?

Solution for Exercise 18

Harald: Well, this exercise looks unproblematic.

Alfrothul (pragmatic): Let’s start at the root of the tree:

(x : int), (y : char), . |- fun x -> not x : bool -> bool

Harald: This looks right – the type environment features the type of x and of y, the expression is fun x -> not x, and the type is bool -> bool.

Brynja: Right, but wait. The following root is just as plausible:

(y : char), (x : int), . |- fun x -> not x : bool -> bool

Alfrothul: True, but it doesn’t matter since x and y are distinct.

Harald: Right. If the type environment is, e.g., (z : char), (z : string), . then the expression z has type char, whereas it has type string in the type environment (z : string), (z : char), .. To wit, here is the first proof tree, applying LOOKUP_FOUND, where x is instantiated with z, t is instantiated with char, G is instantiated with (z : string), ., x is instantiated with z, and t is instantiated with char:

LOOKUP_FOUND ---------------------------------------
             (z : char), (z : string), . |- z : char

Alfrothul: And here is the second proof tree, also applying LOOKUP_FOUND, where x is instantiated with z, t is instantiated with string, G is instantiated with (z : char), ., x is instantiated with z, and t is instantiated with string:

LOOKUP_FOUND -----------------------------------------
             (z : string), (z : char), . |- z : string

Brynja: OK, so it doesn’t matter which root we start with. Let’s take yours, Alfrothul.

Alfrothul: Thank you. There is only one typing rule we can apply, namely FUN, where G is instantiated with (x : int), (y : char), ., x is instantiated with x, e is instantiated with not x, t1 is instantiated with bool, and t2 is instantiated with bool:

    (x : bool), (x : int), (y : char), . |- not x : bool
FUN ---------------------------------------------------------
    (x : int), (y : char), . |- fun x -> not x : bool -> bool

Harald: And now there is only one typing rule we can apply, namely NOT, where G is instantiated with (x : bool), (x : int), (y : char), . and e is instantiated with x:

    (x : bool), (x : int), (y : char), . |- x : bool
NOT ----------------------------------------------------
    (x : bool), (x : int), (y : char), . |- not x : bool
FUN -------------------------------------------------------------------
    (x : int), (y : char), . |- fun x -> not x : bool -> bool

Alfrothul: There is still only one typing rule we can apply, namely LOOKUP_FOUND, where G is instantiated with (x : int), (y : char), ., x is instantiated with x, and t is instantiated with bool:

LOOKUP_FOUND ------------------------------------------------
             (x : bool), (x : int), (y : char), . |- x : bool
         NOT ----------------------------------------------------
             (x : bool), (x : int), (y : char), . |- not x : bool
         FUN -------------------------------------------------------------------
             (x : int), (y : char), . |- fun x -> not x : bool -> bool

Harald: And we are done. The proof tree is complete, which proves that in the type environment (x : int), (y : char), ., the expression fun x -> not x is well typed and has type bool -> bool.

Alfrothul: If we had started with Brynja’s root, we would have done through the same steps and would have reached the same conclusion, i.e., in the type environment (y : char), (x : int), ., the expression fun x -> not x is well typed and has type bool -> bool.

Brynja: What is the point of this exercise?

Mimer: The point is to crystallize that the type environment is extended on its left, not on its right, which is something that beginners tend to do somehow.

Brynja: Eeek. If the environment was extended on the right, the expression would be deemed to be ill-typed.

Harald: Beg pardon?

Brynja: The point is that we would come up with:

(x : int), (y : char), (x : bool), . |- not x : bool

Harald: I see, but the environment is incorrectly extended.

Brynja: And that is the point of this exercise. We could then only apply the NOT rule, where G is instantiated with (x : int), (y : char), (x : bool), . and e is instantiated with bool:

    (x : int), (y : char), (x : bool), . |- x : bool
NOT ----------------------------------------------------
    (x : int), (y : char), (x : bool), . |- not x : bool

Alfrothul: Right. And then we could only apply the LOOKUP_FOUND rule, except that we could not since x has type int in the type environment, and therefore it cannot have type bool.

Interlude

Brynja: Still about Exercise 18, what if we didn’t have the NOT rule, but not was declared in the type environment?

Harald: The question would then be, in a type environment where x has type int and not has type bool -> bool, does the expression fun x -> not x have type bool -> bool or is it ill-typed. That looks unproblematic too.

Alfrothul (pragmatic): Let’s start at the root of the tree:

(x : int), (not : bool -> bool), . |- fun x -> not x : bool -> bool

Harald: This looks right – the type environment features the type of x and of not, the expression is fun x -> not x, and the type is bool -> bool.

Brynja: Right, and since not and x are distinct, the order in the type environment does not matter.

Alfrothul: There is only one typing rule we can apply, namely FUN, where G is instantiated with (x : int), (not : bool -> bool), ., x is instantiated with x, e is instantiated with not x, t1 is instantiated with bool and t2 is instantiated with bool:

    (x : bool), (x : int), (not : bool -> bool), . |- not x : bool
FUN -------------------------------------------------------------------
    (x : int), (not : bool -> bool), . |- fun x -> not x : bool -> bool

Harald: And now there is only one typing rule we can apply, namely APP, where G is instantiated with (x : bool), (x : int), (not : bool -> bool), ., e0 is instantiated with not, e1 is instantiated with x, t1 is unspecified yet, so let’s call it uy, and t2 is instantiated with bool:

    (x : bool), (x : int), (not : bool -> bool), . |- not : uy -> bool    (x : bool), (x : int), (not : bool -> bool), . |- x : uy
APP ------------------------------------------------------------------------------------------------------------------------------
    (x : bool), (x : int), (not : bool -> bool), . |- not x : bool
FUN -------------------------------------------------------------------
    (x : int), (not : bool -> bool), . |- fun x -> not x : bool -> bool

Alfrothul: Unspecified, unspecified – uy can only be bool.

Harald (calmly): You are probably right, but let’s not try to dance faster than the music. Let’s continue to play the game and consider the left branch.

Alfrothul (interrupting): How about we consider the right branch first? Because the LOOKUP_FOUND applies straight away, where x is instantiated with x, t is instantiated with bool, G is instantiated with (x : int), (not : bool -> bool), ., x is instantiated with x, and t is instantiated with uy.

Harald: True that:

                                                                          LOOKUP_FOUND --------------------------------------------------------
    (x : bool), (x : int), (not : bool -> bool), . |- not : uy -> bool                 (x : bool), (x : int), (not : bool -> bool), . |- x : uy
APP -------------------------------------------------------------------------------------------------------------------------------------------
    (x : bool), (x : int), (not : bool -> bool), . |- not x : bool
FUN -------------------------------------------------------------------
    (x : int), (not : bool -> bool), . |- fun x -> not x : bool -> bool

Alfrothul: And since in the application of LOOKUP_FOUND, t is instantiated both with bool and with uy, uy is constrained to be bool.

Harald: So I guess we can either update the tree, or remember that uy stands for bool.

Brynja (prudent): Rather the latter – let’s not mess up with the tree.

Harald: Be that as it may, we can consider the left branch. We can only apply LOOKUP_NOT_FOUND_YET, where x' is instantiated with x, t' is instantiated with bool, G is instantiated with (x : int), (not : bool -> bool), ., x is instantiated with not, and t is instantiated with uy -> bool:

                     (x : int), (not : bool -> bool), . |- not : uy -> bool
LOOKUP_NOT_FOUND_YET ------------------------------------------------------------------    LOOKUP_FOUND --------------------------------------------------------
                     (x : bool), (x : int), (not : bool -> bool), . |- not : uy -> bool                 (x : bool), (x : int), (not : bool -> bool), . |- x : uy
                 APP -------------------------------------------------------------------------------------------------------------------------------------------
                     (x : bool), (x : int), (not : bool -> bool), . |- not x : bool
                 FUN -------------------------------------------------------------------
                     (x : int), (not : bool -> bool), . |- fun x -> not x : bool -> bool

Harald (continuing): You’re sure you don’t want to change uy to bool?

Brynja: Quite sure – we are bound to miss an occurrence or to overwrite one of the occurrences of another variable, and then what.

Alfrothul (unabated): Well, we can still only apply LOOKUP_NOT_FOUND_YET, where x' is instantiated with x, t' is instantiated with int, G is instantiated with (not : bool -> bool), ., x is instantiated with not, and t is instantiated with uy -> bool:

                     (not : bool -> bool), . |- not : uy -> bool
LOOKUP_NOT_FOUND_YET ------------------------------------------------------
                     (x : int), (not : bool -> bool), . |- not : uy -> bool
LOOKUP_NOT_FOUND_YET ------------------------------------------------------------------    LOOKUP_FOUND --------------------------------------------------------
                     (x : bool), (x : int), (not : bool -> bool), . |- not : uy -> bool                 (x : bool), (x : int), (not : bool -> bool), . |- x : uy
                 APP -------------------------------------------------------------------------------------------------------------------------------------------
                     (x : bool), (x : int), (not : bool -> bool), . |- not x : bool
                 FUN -------------------------------------------------------------------
                     (x : int), (not : bool -> bool), . |- fun x -> not x : bool -> bool

Harald: Aha, and now we can apply LOOKUP_FOUND, where x is instantiated with not, t is instantiated with bool -> bool, G is instantiated with ., x is instantiated with not, and t is instantiated with uy -> bool, which is OK since uy stands for bool:

        LOOKUP_FOUND -------------------------------------------
                     (not : bool -> bool), . |- not : uy -> bool
LOOKUP_NOT_FOUND_YET ------------------------------------------------------
                     (x : int), (not : bool -> bool), . |- not : uy -> bool
LOOKUP_NOT_FOUND_YET ------------------------------------------------------------------    LOOKUP_FOUND --------------------------------------------------------
                     (x : bool), (x : int), (not : bool -> bool), . |- not : uy -> bool                 (x : bool), (x : int), (not : bool -> bool), . |- x : uy
                 APP -------------------------------------------------------------------------------------------------------------------------------------------
                     (x : bool), (x : int), (not : bool -> bool), . |- not x : bool
                 FUN -------------------------------------------------------------------
                     (x : int), (not : bool -> bool), . |- fun x -> not x : bool -> bool

Alfrothul: And we are pretty much done, since the proof tree is complete. For clarity, let us draw it again by replacing uy with bool:

        LOOKUP_FOUND -------------------------------------------
                     (not : bool -> bool), . |- not : bool -> bool
LOOKUP_NOT_FOUND_YET --------------------------------------------------------
                     (x : int), (not : bool -> bool), . |- not : bool -> bool
LOOKUP_NOT_FOUND_YET --------------------------------------------------------------------    LOOKUP_FOUND ----------------------------------------------------------
                     (x : bool), (x : int), (not : bool -> bool), . |- not : bool -> bool                 (x : bool), (x : int), (not : bool -> bool), . |- x : bool
                 APP -----------------------------------------------------------------------------------------------------------------------------------------------
                     (x : bool), (x : int), (not : bool -> bool), . |- not x : bool
                 FUN -------------------------------------------------------------------
                     (x : int), (not : bool -> bool), . |- fun x -> not x : bool -> bool

Harald: The existence of this tree proves that in the type environment (x : int), (not : bool -> bool), ., the expression fun x -> not x is well typed and has type bool -> bool.

Version

Fixed a typo [07 Feb 2020]

Added Exercise 18 and its solution as well as the subsequent interlude [01 Feb 2020]

Added Exercise 0 [31 Jan 2020]

Created [03 Feb 2019]

Table Of Contents

Previous topic

Unit tests

Next topic

YSC1212 Lecture Notes, Week 04