7 lines of code, 3 minutes: Implement a programming language (2010)
6 hours ago
- #lambda-calculus
- #interpreter
- #programming-languages
- The lambda calculus is a minimalist, higher-order functional programming language known as the lambda calculus.
- The lambda calculus lives at the core of major functional languages like Haskell, Scheme, and ML, and also exists within JavaScript, Python, and Ruby.
- Alonzo Church developed the lambda calculus in 1929, before computers existed, as a mathematical notation for reasoning about functions.
- Alan Turing defined the Turing machine, which became the first accepted definition of a general-purpose computer, and it was soon discovered that the lambda calculus and the Turing machine were equivalent.
- The lambda calculus has only three kinds of expressions: variable references, anonymous functions (lambda terms), and function calls.
- Anonymous functions are written with a 'λ-dot' notation, e.g., '(λ v . e)' is the function that accepts an argument 'v' and returns the value of 'e'.
- Function calls are written by putting two expressions adjacent, e.g., '(f e)'.
- The lambda calculus achieves Turing-equivalence through two of the coolest programming hacks out there: Church encodings and the Y combinator.
- A 7-line, 3-minute interpreter for the lambda calculus in R৫RS Scheme demonstrates an environment-based denotational interpreter.
- The interpreter uses two core functions: 'eval' and 'apply', with conceptual signatures: 'eval : Expression * Environment -> Value' and 'apply : Value * Value -> Value'.
- Racket provides a match construct that cleans up the interpreter, making it cleaner and easier to understand.
- The eval/apply design of the interpreter scales to much bigger languages; for example, in about 100 lines we can implement an interpreter for a sizable subset of Scheme itself.
- We can add various expression forms (variables, constants, primitives, conditionals, bindings, mutation, sequencing) and top-level forms (function definition, global definition) to the language.
- The entire interpreter, with test harness and test cases, fits in a concise implementation.
- Modifying the last interpreter allows quickly testing out new ideas for programming languages by separating syntactic design from semantic design.