Module 14: Type checking first-class functions

This module is due on Tuesday, November 1 at 1:15pm.

In this module we will continue exploring how to type check a language with first-class functions. This language, together with its type system, is known as the simply-typed lambda calculus, or STLC1 (extended with integers and addition).

Types for lambdas and applications

You already know how to typecheck numeric literals, arithmetic operations such as addition, and variables; the things we need to focus on are the language features specifically having to do with functions, namely, lambdas and applications. Let’s think more carefully about how to give these things types. In answering the following questions you may want to refer back to the examples in the previous module for inspiration.

In the following, the letter e will stand for any expression, and T will stand for any type.

So we know how to infer the type of an application or a lambda with a type annotation, and how to check the type of a lambda without a type annotation.

What about inferring the type of a lambda without a type annotation? As you saw in the previous module, this can sometimes be done, but it is tricky. For example, it is possible to infer that (^x -> (^f -> f x + x)) has type Int -> (Int -> Int) -> Int, but this requires some nontrivial deduction. For the purposes of this module, we are not going to do this. Instead, we will just throw up our hands and refuse to infer the type of a lambda without a type annotation. (However, if you are interested to know how this “nontrivial deduction” (called type reconstruction) works in general, I would be happy to explain it. In fact, implementing this for some nontrivial language could be a nice part of a final project.)

A type checker for STLC

Updating the interpreter

Now that we have a type checker, the interpreter doesn’t have to do so much work. However, unlike with previous interpreters, we can’t really get rid of the Value type. When we interpreted Arith with booleans and integers, for example, we could just represent all values as integers; but now we have integers and closures, which are so different that there’s no appropriate type we can use to easily represent both. So your interpreter will still need to pattern-match on Values. Nonetheless, it can still be simplified quite a bit:

Correcting a few issues

Lambda calculus in the real world

Take a look at this page from the GHC wiki. Haskell syntax is complicated, but it all ultimately gets desugared to this very simple data type Expr.

Feedback


  1. I always get STLC and SLTC mixed up.