That’s the end! In this course, we learned about

Functions, as embodied in the λ-calculus. This single concept is powerful enough to express any computation a Turing machine can, so learning how to define the operational semantics of functions is a fast-track to learning how to define the operational semantics of a full programming language.

Types: We learned how to define type systems, which enable us to prove some static guarantees about a program’s behavior (type safety or “well-typed programs don’t get stuck”). But our first attempt was also unduly restrictive; we looked at several ways of increasing expressivity, in both the mathematical sense (is it Turing complete?) and practical sense (would you want to program in this?).

Effects: Whether untyped or typed, we actually can’t express everything we want to, if we use the stricter notion of macro-expressibility: we can’t express effects like state and control. We can define these by translation into continuation-passing style, or macro-express them in terms of a “universal” effect like gen/yield.

What’s next? If you are interested in reading more about…

  • λ-calculus: Lambda Calculus and Combinators: An Introduction, 2nd ed., Hindley and Seldin

  • Denotational semantics: Formal Semantics of Programming Languages, Winskel

  • Types: The rest of TAPL

  • Effects: I’m not aware of a textbook or tutorial introduction; a nice recent paper is “On the expressive power of user-defined effects”, Forster et al.

  • Compilers: Compiling with Continuations, Appel