- Function Definition / Abstraction (
$λ x . e$ ) - Function Application (
$e \ e$ ) - Parameters / Variables (
$x$ )
- Booleans
- Numerals
- Data Structures
- Control Flow
- …
- If it can be computed, it can be computed in Lambda Calculus!
\begin{align*}
\onslide<1->{ & ( λ \color<3->{orange}{p} . & λ q . & \color<3->{orange}{p} & ) & & \color<3->{orange}{a} & & b & & \onslide<2->{ \text{Substitute
\onslide<4->{ & ( & λ \color<6->{blue}{q} . & \color<4->{orange}{a} & ) && & & \color<6->{blue}{b} & & \onslide<5->{ \text{Substitute
- $λ p . λ q . p$
- Is a function that returns a function ($λ q . p$)
- $a , b$
- Some variables (defined somewhere else)
- $p$
- Is a variable that is bound to the parameter with the same name
- Deepen our intiution
- Later move on to the Simply Typed Lambda Calculus
- Why do we need types?
- How does a type checker work?
- How does it restrict the programs we might write?
- We’ll do Math Driven Development
- Look at the concepts in math first, then translate them to Haskell
\begin{align*}
e ::= & & \text{Expressions:}
& \ x & \text{Variable} \
& \ λ x.e & \text{Abstraction} \
& \ e \ e & \text{Application}
\end{align*}
- $λ x.e$
- Function Definition
- $e \ e$
- Function Application (Function Call)
digraph untyped_lambda_calculus_ast {
App [label = "Application"];
Lam [label = "Lambda"]
Param [label = "Parameter: x"]
VarX [label = "Variable: x"]
VarY [label = "Variable: y"]
App -> {Lam VarY}
Lam -> {Param VarX}
}
- Identity function
$(λ x . x)$ is applied to a variable ($y$ )
module UntypedSyntax where
type Name = String
data Expr -- $e ::= \ \ \ \ \ \ \ \ \ \ \ \text{Expressions:}$
= Var Name -- $x \ \ \ \ \ \ \ \ \ \ \ \text{Variable}$
| Lambda Name -- $\lambda x.e \ \ \ \ \ \ \ \text{Abstraction}$
Expr
| App Expr -- $e \ e \ \ \ \ \ \ \ \ \ \text{Application}$
Expr
deriving (Eq, Show)
module UntypedSyntaxExamples where
import UntypedSyntax
-- $id \equiv \lambda x . x$
id :: Expr
id = Lambda "x" $ Var "x"
-- $true \equiv \lambda p . \lambda q . p$
true :: Expr
true = Lambda "p" (Lambda "q" (Var "p"))
-- $false \equiv \lambda p . \lambda q . q$
false :: Expr
false = Lambda "p" (Lambda "q" (Var "q"))
-- $and \equiv \lambda p . \lambda q . p \ q \ p$
and :: Expr
and = Lambda "p" $ Lambda "q" $ App (App (Var "p") (Var "q")) (Var "p")
\begin{align*}
\frac{}{Axiom} & \quad & \text{(A1)} \
\frac{Antecedent}{Conclusion} & \quad & \text{(A2)} \ \
\end{align*}
- Axiom
- Rule without Precondition
- Antecedent
- Precondition - if it’s fulfilled this rule applies
- Conclusion
- What follows from this rule
- A1, A2
- Names for the rules
\begin{align*}
\frac{}{0 : \mathtt{Nat}} & \quad & \text{(A1)} \
\frac{n : \mathtt{Nat}}{\mathtt{succ}(n) : \mathtt{Nat}} & \quad & \text{(A2)} \ \
\end{align*}
\onslide<2->
\begin{equation*} \dfrac { \quad \dfrac { \quad \dfrac{} { 0 : Nat } \quad \text{(A1)} } { succ(0) : Nat } \quad \text{(A2)} } { succ(succ(0)) : Nat } \quad \text{(A2)} \end{equation*}
\vspace{-0.5cm} \onslide<1->
- A1
- $0$ is a natural number (by definition)
- A2
- The successor of a natural number is a natural number
\onslide<2->
- $→$
- Thus the successor of the successor of $0$ ($2$) must be a natural number
\begin{align*}
\frac{e_1 → e_1’}{e_1 e_2 → e_1’ e_2} & \quad & \text{E-App1} \
\end{align*}
- Under the condition that
$e_1$ can be reduced further, do it.
\begin{equation*} \frac{e_1 → e_1’}{e_1 e_2 → e_1’ e_2} \end{equation*}
\begin{align*}
& \overbrace{\alert<2->{((λ x.x) \ (λ y.y))}}e_1 \ e_2
\onslide<3->{→ & \alert<3->{(λ y.y)} \ z}
\end{align*}
\begin{align*}
\frac{e_2 → e_2’}{v_1 e_2 → v_1 e_2’} & \quad & \text{E-App2} \
\end{align*}
- Under the condition that
$e_2$ can be reduced further and$v_1$ is a value, do it. - “Bare” Untyped Lambda Calculus:
- Only Lambdas (functions) are values.
- But you can add Ints, Booleans, etc. (“Enriched Untyped Lambda Calculus”)
\begin{align*}
\frac{e_2 → e_2’}{v_1 e_2 → v_1 e_2’} & \quad & \text{E-App2} \
\end{align*}
\vspace{-0.5cm}
\begin{align*}
& \overbrace{(λ x.x)}v_1 \ \overbrace{\alert<2->{((λ y.y) \ 42)}}e_2
\onslide<3->{→ & (λ x.x) \ \alert<3->{42}}
\end{align*}
\vspace{-0.5cm} \onslide<4->
- We evaluate the parameter before applying the function: Eager Evaluation!
\begin{align*}
{(λ x . e) v → [x / v] e } & \quad & \text{E-AppLam} \
\end{align*}
- If a lambda (function) is applied to a value, substitute that value for it’s parameter.
- “substitute” : replace it for every occurence in the lambda’s body
\begin{align*}
{(λ x . e) v → [x / v] e } & \quad & \text{E-AppLam} \
\end{align*}
\begin{align*}
& \overbrace{(\alert<2->{λ x.} λ y.\alert<2->{x})}λ x.e \ \overbrace{\alert<2->{z}}v
\onslide<3->{→ & λ y .\alert<3->{z}}
\end{align*}
module UntypedEval where
import UntypedSyntax
eval :: Expr -> Expr
-- No rule for variables
eval variable@(Var _) = variable
-- No rule for lambdas
eval lambda@(Lambda _ _) = lambda
eval (App e1 e2)
--
-- $ \frac{e_1 \to e_1'}{e_1 e_2 \to e_1' e_2} \quad (E-App1) $
--
=
let e1' = eval e1
--
-- $ \frac{e_2 \to e_2'}{v_1 e_2 \to v_1 e_2'} \quad (E-App2) $
--
in let e2' = eval e2
in case e1'
of
--
-- $ {(\lambda x . e) v \to [x / v] e } \quad (E-AppLam) $
--
(Lambda x e1'_body) -> eval $ substitute x e2' e1'_body
e1' -> App e1' e2'
substitute :: Name -> Expr -> Expr -> Expr
--
-- If the Name matches: Substitute this Var by it's substitution
-- Otherwise: Leave it as is
--
substitute name substitution var@(Var varName)
| name == varName = substitution
| otherwise = var
--
-- Recursively substitute in both parts of Applications
--
substitute name substitution (App term1 term2) =
App (substitute name substitution term1) (substitute name substitution term2)
--
-- Only substitute in Lambda's body, if the parameter doesn't
-- redefine the Name in it's scope
--
substitute name substitution lambda@(Lambda varName term) =
if name == varName
then lambda
else Lambda varName (substitute name substitution term)
module UntypedEvalExamplesSpec where
import NaiveUntypedEval
import Prelude hiding (and)
import Test.Hspec
import UntypedSyntax
import UntypedSyntaxExamples
main :: IO ()
main = hspec spec
spec :: Spec
spec =
describe "eval" $
it "should evaluate these terms" $ do
--
-- $a \to a $
--
eval (Var "a") `shouldBe` Var "a"
--
-- $true \equiv \lambda p . \lambda q . p$
--
-- $true \ a \ b \to a$
--
eval (App (App true (Var "a")) (Var "b")) `shouldBe` Var "a"
--
-- $false \equiv \lambda p . \lambda q . q$
--
-- $and \equiv \lambda p . \lambda q . p \ q \ p$
--
-- $and \ true \ false \to false$
--
eval (App (App and true) false) `shouldBe`
Lambda "p" (Lambda "q" (Var "q"))
\begin{align*}
e ::= & & \text{Expressions:}
& \ x & \text{Variable} \
& \ λ x \alert{:τ}.e & \text{Abstraction} \
& \ e \ e & \text{Application}
\end{align*}
- $τ$
- Type of the parameter $x$
Bool
,Int
, …
A Type is a set of values that an expression may return:
- Bool
- True, False
- Int
- $[-229 .. 229-1]$ (in Haskell, `Data.Int`)
Simple types don’t have parameters, no polymorphism:
Bool
,Int
- have no parameters $→$ simple types
Maybe a
- takes a type parameter ($a$) $→$ not a simple type
a -> a
- is polymorphic $→$ not a simple type
**Progress** : If an expression is well typed then either it is a value, or it can be further evaluated by an available evaluation rule.
- A well typed (typeable) program never gets “stuck”.
**Preservation** : If an expression $e$ has type $τ$ , and is evaluated to $e’$ , then $e’$ has type $τ$ .
-
$e ≡ (λ x: Int.x) 1$ and$e’ ≡ 1$ have both the same type:Int
id :: a -> a
id a = a
- It strongly depends on the type system if this is allowed or not.
- In Simply Typed Lambda Calculus it’s not!
- No polymorphic types …
- Type checking is done upfront
\begin{align*}
\frac{x:τ ∈ Γ}{Γ \vdash x:τ} & \quad & \text{T-Var} \
\end{align*}
- Γ
- The Typing Environment, a list of $(Variable : Type)$ pairs (associations)
- Think of a map:
$Variable \mapsto Type$
- Think of a map:
- Condition
- If $(x, τ)$ is in the Typing Environment
- Conclusion
- $x$ has type $τ$
\begin{align*}
\frac{x:τ ∈ Γ}{Γ \vdash x:τ} & \quad & \text{T-Var} \
\end{align*}
\begin{align*} \underbrace{λ x:Int}Γ’ = Γ , x:Int. \underbrace{λ y:Bool}Γ” = Γ’, y:Bool.\underbrace{x}Γ” \vdash x:Int \end{align*}
- $λ x:Int$
- Add $x:Int$ to the Typing Environment ($Γ$)
- $x$
- We know from the Typing Environment ($Γ”$) that $x$ has type $Int$
\vspace{-0.5cm}
\begin{align*}
Γ \vdash n : \text{Int} & \quad & \text{T-Int} \
Γ \vdash \text{True} : \text{Bool} & \quad & \text{T-True} \ \
Γ \vdash \text{False} : \text{Bool} & \quad & \text{T-False} \ \
\end{align*}
\vspace{-1.5cm}
True
,False
- literals / constants are of type
Bool
- $n$
- number literals / constants are of
Int
- We handle Type Constructors like variables
- Think:
$Γ ≡ ∅ , True : Bool, False : Bool, 0 : Int, 1 : Int, …$
\begin{align*}
Γ \vdash n : \text{Int} & \quad & \text{T-Int} \
Γ \vdash \text{True} : \text{Bool} & \quad & \text{T-True}
\end{align*}
\begin{align*}
& Γ ≡ ∅ , \textcolor<2->{mLightGreen}{True : Bool}, False : Bool, 0 : Int, \textcolor<3->{mLightBrown}{1 : Int}, … \
& \textcolor<2->{mLightGreen}{True} \
& \textcolor<3->{mLightBrown}{1}
\end{align*}
\begin{align*}
\frac{Γ, x : τ_1 \vdash e : τ_2}{Γ \vdash λ x:τ_1 . e : τ_1 → τ_2 } & \quad & \text{T-Lam} \
\end{align*}
- Condition
- With $x : τ_1$ in the Typing Environment, $e$ has type τ_2
- Conclusion
- $λ x:τ_1 . e$ has type $τ_1 → τ_2$
- Because
$e$ has type τ_2 if$x$ has type τ_1
\begin{align*}
\frac{Γ, x : τ_1 \vdash e : τ_2}{Γ \vdash λ x:τ_1 . e : τ_1 → τ_2 } & \quad & \text{T-Lam} \
\end{align*}
\begin{align*}
& λ x: \overbrace{Int}τ_1 . \overbrace{x}e & : & \onslide<1-2>{?} \onslide<3->{\overbrace{Int → Int}τ_1 → τ_2 } \
\onslide<2->{& \frac{Γ, x : \overbrace{Int}τ_1 \vdash e : \overbrace{Int}τ_2}{Γ \vdash λ x: \underbrace{Int}τ_1 . e : \underbrace{Int → Int}τ_1 → τ_2 }}
\end{align*}
\begin{align*}
\frac{Γ \vdash e_1 : τ_1 → τ_2 \quad Γ \vdash e_2 : τ_1}{Γ \vdash e_1 e_2 : τ_2} & \quad & \text{T-App} \
\end{align*}
- Condition
- If $e_1$ is a function of type $τ_1 → τ_2$ and $e_2$ has type $τ_2$
- Conclusion
- Then the type of $e_1 e_2$ (function application) is $τ_2$
id' :: Int -> Int
id' i = i
1 :: Int
(id' 1) :: Int
\begin{align*}
\frac{Γ \vdash e_1 : τ_1 → τ_2 \quad Γ \vdash e_2 : τ_1}{Γ \vdash e_1 e_2 : τ_2} & \quad & \text{T-App} \
\end{align*}
\begin{align*}
& \overbrace{(λ x:Int. True)}e_1 \overbrace{42}e_2 & : & \onslide<1-2>{?} \onslide<3->{\overbrace{Bool}τ_2} \
\onslide<2->{ & \frac{Γ \vdash \overbrace{(λ x:Int. True)}e_1 : \overbrace{Int → Bool}τ_1 → τ_2 \quad Γ \vdash \overbrace{42}e_2 : \overbrace{Int}τ_1}{Γ \vdash \underbrace{(λ x:Int. True)}e_1 \underbrace{42}e_2 : \underbrace{Bool}τ_2}}
\end{align*}
module TypedSyntax where
import qualified Data.Map.Strict as Map
type Name = String
type Error = String
data Expr -- $e ::= \quad \quad \quad \quad \quad \quad \quad \quad Expressions:$
= IntValue Int -- $[-2^{29} .. 2^{29}-1] \quad \quad \quad \quad \quad \text{Integer Literal}$
| BoolValue Bool -- $True \ | \ False \quad \quad \quad \quad \quad \quad \ \text{Boolean Literal}$
| Var Name -- $x \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \ \text{Variable}$
| App Expr -- $e \ e \quad \quad \quad \quad \quad \quad \quad \quad \quad \quad \ \text{Application}$
Expr
| Lambda Name -- $\lambda x \mathbf{:\tau}.e \quad \quad \quad \quad \quad \quad \quad \quad \ \text{Abstraction}$
Type
Expr
deriving (Eq, Show)
type Environment = Map.Map Name Type
data Type -- $\tau ::= \quad \quad \quad \quad \quad \quad Types:$
= TInt -- $Int \quad \quad \quad \quad \quad \quad \text{Integer}$
| TBool -- $Bool \quad \quad \quad \quad \quad \ \text{Boolean}$
| TArr Type -- $\tau_1 \to \tau_2 \quad \quad \quad \ \ \text{Abstraction / Function}$
Type
deriving (Eq, Show)
module TypedCheck where
import Data.Either.Extra
import qualified Data.Map.Strict as Map
import TypedSyntax
check :: Environment -> Expr -> Either Error Type
--
-- $ \Gamma \vdash n : \text{Int} \quad \text{(T-Int)} $
--
check _ (IntValue _) = Right TInt
--
-- $ \Gamma \vdash \text{True} : \text{Bool} \quad \text{(T-True)} $
--
check _ (BoolValue True) = Right TBool
--
-- $ \Gamma \vdash \text{False} : \text{Bool} \quad \text{(T-False)} $
--
check _ (BoolValue False) = Right TBool
--
-- $ \frac{\Gamma, x : \tau_1 \vdash e : \tau_2}{\Gamma \vdash \lambda x:\tau_1 . e : \tau_1 \rightarrow \tau_2 } \quad \text{(T-Lam)} $
--
check env (Lambda x t1 e) = do
t2 <- check (Map.insert x t1 env) e
return $ TArr t1 t2
--
-- $ \frac{\Gamma \vdash e_1 : \tau_1 \rightarrow \tau_2 \quad \Gamma \vdash e_2 : \tau_1}{\Gamma \vdash e_1 e_2 : \tau_2} \quad \text{(T-App)} $
--
check env (App e1 e2) = do
te1 <- check env e1
case te1 of
(TArr t1 t2) -> do
te2 <- check env e2
if t1 == te2
then Right t2
else Left $ "Expected " ++ (show t1) ++ " but got : " ++ (show te2)
_ -> Left $ "Expected TArr but got : " ++ (show te1)
--
-- $ \frac{x:\tau \in \Gamma}{\Gamma \vdash x:\tau} \quad \text{(T-Var)} $
--
check env (Var x) = find env x
find :: Environment -> Name -> Either Name Type
find env name = maybeToEither "Var not found!" (Map.lookup name env)
module TypedCheckExamplesSpec where
import Test.Hspec
import TypedCheck
import TypedSyntax
import qualified Data.Map.Strict as Map
main :: IO ()
main = hspec spec
spec :: Spec
spec = do
describe "check" $
it "should type check these terms" $
--
-- $(\lambda x:Int . x) \ 42 :: Int $
--
do
check Map.empty (App (Lambda "x" TInt (Var "x")) (IntValue 5))
`shouldBe` Right TInt
--
-- Does not type check: $(\lambda x:Bool . x) \ 42$
--
check Map.empty (App (Lambda "x" TBool (Var "x")) (IntValue 5))
`shouldBe` Left "Expected TBool but got : TInt"
--
-- Does not type check: $42 \ False$
--
check Map.empty (App (IntValue 42) (BoolValue False)) `shouldBe`
Left "Expected TArr but got : TInt"
- Hope you enjoyed this talk and learned something new.
- Hope it wasn’t too much math and dusty formulas … :)
- Easy to understand
- Types systems explained by building interpreters / checkers and proving properties
- Very “mathematical”, but very complete and self-contained
- Starts with the Lambda Calculus and goes all the way down to a full Haskell compiler
- Available for free - Not finished, yet