Building a type checker in Haskell by Christoffer Ekeroth

December 3, 2024

Christoffer Ekeroth aims to demystify type checkers for all experience levels and discusses the role of type checking in program compilation, emphasizing its position following parsing using an abstract syntax tree (AST). He outlines the process of creating a type checker for a variant of lambda calculus, incorporating extensions for boolean logic and integers. Christoffer explains key concepts such as typing rules for conditionals and functions, the necessity of a context for tracking variables, and the handling of function applications. He illustrates the type-checking process with examples, demonstrating how the system can provide type assurances, albeit lacking Turing completeness.

Building a type checker in Haskell: A comprehensive overview

What is type checking?

Type checking is a critical step in the compilation process that ensures code correctness by verifying that all expressions adhere to their expected types. Christoffer Ekeroth explained that type checking occurs after the parser generates an Abstract Syntax Tree (AST) from the source code. The type checker traverses this AST, evaluating terms to either return their type if they are well-typed or produce an error otherwise.

For the talk, Christoffer focused on a simple type checker designed to handle a variant of the lambda calculus, augmented with basic types like integers and booleans. By doing so, he created a practical yet approachable context for discussing type checking in Haskell.

 

The basics: Handling literals and conditionals

Christoffer began by outlining how the type checker handles fundamental types such as integers and booleans, introducing the type system for these literals. He then delved into type-checking conditionals (i.e., if-then-else constructs).

Typing Rules for Conditionals:

  • The condition must evaluate to a boolean type.
  • Both branches of the conditional must return the same type.
  • If these conditions are met, the type of the conditional matches the type of the branches.

Using a recursive approach, the type checker evaluates each component of the conditional by traversing the AST. This example highlighted how Haskell’s pattern matching and recursion elegantly support such operations.

 

Type-checking functions: The Lambda Calculus

Christoffer introduced the complexities of type-checking lambda functions, which consist of a variable (the parameter) and a body (the function’s expression).

  • Functions require explicit type annotations for the input variable.
  • The type checker substitutes the function argument with the actual input and verifies the body’s type.

He illustrated this process by switching from a bottom-up approach to a top-down strategy for type inference. In Haskell, lambda functions are represented as expressions that capture both input type and output type, making the process intuitive.

 

Tracking variables with contexts

Handling variables in a type checker requires maintaining a context, which maps variable names to their respective types.

  • The context ensures variables introduced by lambdas are tracked throughout the program.
  • If a variable is not found in the context, the type checker raises a type error.

Christoffer expanded the term type to include variables represented as strings and showcased how the type checker extends the context during function applications. This step is crucial for ensuring type correctness when functions are applied to arguments.

 

Applications and type matching

Christoffer demonstrated type-checking function applications by adding an application constructor to the term type.

  • Before applying a function, the type checker verifies that the term is indeed a function and that the argument’s type matches the function’s input type.
  • If the check fails, it returns Nothing, signifying a type error.

For example, if the Identity function expects an integer but receives a boolean, the type checker will fail gracefully, ensuring only well-typed expressions are evaluated.

 

Challenges and resources for aspiring type checkers

While the type checker built during the presentation lacked recursion, preventing Turing completeness, it offered valuable insights into how simple type systems operate. Christoffer encouraged further exploration into more advanced type checkers by recommending the following resources:

 

Key takeaways and final thoughts

  1. Type checking simplified: Christoffer’s presentation demystified type checking by illustrating how it’s implemented in a functional language like Haskell using simple rules and recursion.
  2. Practical applications: The type checker, though simple, showcased Haskell’s powerful type system and how it can enforce correctness at compile time.
  3. Further exploration: Christoffer emphasized that while the demo was introductory, the concepts form the foundation of more complex systems found in compilers like those for Standard ML or OCaml.

For those interested in functional programming and programming language theory, Christoffer’s talk was a testament to how approachable type checking can be when broken down with clarity and precision. He shared his enthusiasm for further exploring languages like OCaml, highlighting the vibrant community surrounding programming language theory.

 

Additional resources

Check out more from the MeetUp Func Prog Sweden. Func Prog Sweden is the community for anyone interested in functional programming. At the MeetUps the community explore different functional languages like Erlang, Elixir, Haskell, Scala, Clojure, OCaml, F# and more.