Lambda Calculus: Implementation Techniques and a Proof. COS 441 Slides 15

Similar documents
M. Snyder, George Mason University LAMBDA CALCULUS. (untyped)

Programming Languages Fall 2014

Fall 2013 Midterm Exam 10/22/13. This is a closed-book, closed-notes exam. Problem Points Score. Various definitions are provided in the exam.

CMSC 336: Type Systems for Programming Languages Lecture 5: Simply Typed Lambda Calculus Acar & Ahmed January 24, 2008

Lesson 4 Typed Arithmetic Typed Lambda Calculus

Programming Languages

More Untyped Lambda Calculus & Simply Typed Lambda Calculus

Lambda Calculus and Type Inference

Formal Semantics. Prof. Clarkson Fall Today s music: Down to Earth by Peter Gabriel from the WALL-E soundtrack

The Substitution Model

Untyped Lambda Calculus

Goal. CS152: Programming Languages. Lecture 15 Parametric Polymorphism. What the Library Likes. What The Client Likes. Start simpler.

Programming Language Features. CMSC 330: Organization of Programming Languages. Turing Completeness. Turing Machine.

CMSC 330: Organization of Programming Languages

Chapter 5: The Untyped Lambda Calculus

CSE 505: Concepts of Programming Languages

1 Introduction. 3 Syntax

Untyped Lambda Calculus

Lambda Calculus. Concepts in Programming Languages Recitation 6:

Lecture 9: Typed Lambda Calculus

COMP80 Lambda Calculus Programming Languages Slides Courtesy of Prof. Sam Guyer Tufts University Computer Science History Big ideas Examples:

The Untyped Lambda Calculus

Subsumption. Principle of safe substitution

Type Systems Winter Semester 2006

Formal Systems and their Applications

More Lambda Calculus and Intro to Type Systems

Costly software bugs that could have been averted with type checking

Variables. Substitution

Writing code that I'm not smart enough to write. A funny thing happened at Lambda Jam

Tradeoffs. CSE 505: Programming Languages. Lecture 15 Subtyping. Where shall we add useful completeness? Where shall we add completeness?

The Substitution Model. Nate Foster Spring 2018

CMSC 330: Organization of Programming Languages. Lambda Calculus

CIS 500 Software Foundations Fall September 25

λ calculus is inconsistent

Mutable References. Chapter 1

5. Introduction to the Lambda Calculus. Oscar Nierstrasz

More Lambda Calculus and Intro to Type Systems

CS-XXX: Graduate Programming Languages. Lecture 9 Simply Typed Lambda Calculus. Dan Grossman 2012

COMP 4161 NICTA Advanced Course. Advanced Topics in Software Verification. Toby Murray, June Andronick, Gerwin Klein

CS 6110 S11 Lecture 25 Typed λ-calculus 6 April 2011

Simon Peyton Jones Microsoft Research. August 2012

dynamically typed dynamically scoped

The Lambda Calculus. 27 September. Fall Software Foundations CIS 500. The lambda-calculus. Announcements

Comp 411 Principles of Programming Languages Lecture 7 Meta-interpreters. Corky Cartwright January 26, 2018

Concepts of programming languages

Type Systems. Pierce Ch. 3, 8, 11, 15 CSE

CMSC 330: Organization of Programming Languages

CIS 500 Software Foundations Fall October 2

CMSC 330: Organization of Programming Languages

Simply-Typed Lambda Calculus

Part III. Chapter 15: Subtyping

CMSC 330: Organization of Programming Languages. Lambda Calculus

Lambda Calculus. Type Systems, Lectures 3. Jevgeni Kabanov Tartu,

CS152: Programming Languages. Lecture 11 STLC Extensions and Related Topics. Dan Grossman Spring 2011

Introduction to the Lambda Calculus

Lecture 13 CIS 341: COMPILERS

Logic and Computation Lecture 20 CSU 290 Spring 2009 (Pucella) Thursday, Mar 12, 2009

More Lambda Calculus and Intro to Type Systems

Recap. Recap. If-then-else expressions. If-then-else expressions. If-then-else expressions. If-then-else expressions

Lecture 9: More Lambda Calculus / Types

- M ::= v (M M) λv. M - Impure versions add constants, but not necessary! - Turing-complete. - true = λ u. λ v. u. - false = λ u. λ v.

Review. CS152: Programming Languages. Lecture 11 STLC Extensions and Related Topics. Let bindings (CBV) Adding Stuff. Booleans and Conditionals

Static Contract Checking for Haskell

Recursively Enumerable Languages, Turing Machines, and Decidability

Type Safety. Java and ML are type safe, or strongly typed, languages. C and C++ are often described as weakly typed languages.

Last class. CS Principles of Programming Languages. Introduction. Outline

CIS 500 Software Foundations Midterm I

Variables and Bindings

Denotational Semantics. Domain Theory

CS 242. Fundamentals. Reading: See last slide

Lambda Calculus and Type Inference

Part III Chapter 15: Subtyping

CSE505, Fall 2012, Midterm Examination October 30, 2012

Static Program Analysis Part 8 control flow analysis

CS 4110 Programming Languages & Logics. Lecture 17 Programming in the λ-calculus

Typed Lambda Calculus and Exception Handling

Lecture 5: The Untyped λ-calculus

COP4020 Programming Languages. Functional Programming Prof. Robert van Engelen

Lecture 13: Subtyping

Constraint-based Analysis. Harry Xu CS 253/INF 212 Spring 2013

(Refer Slide Time: 4:00)

Functional Languages. Hwansoo Han

Types for References, Exceptions and Continuations. Review of Subtyping. Γ e:τ τ <:σ Γ e:σ. Annoucements. How s the midterm going?

Printed by Zach Tatlock Dec 08, 15 22:18 Page 1/11. Lemma subst_only_free: forall e1 e2 x e3, Subst e1 e2 x e3 > ~ free e1 x > e1 = e3. Proof.

The Eval/Apply Cycle Eval. Evaluation and universal machines. Examining the role of Eval. Eval from perspective of language designer

CSE 505: Programming Languages. Lecture 10 Types. Zach Tatlock Winter 2015

From the λ-calculus to Functional Programming Drew McDermott Posted

The Untyped Lambda Calculus

Introduction to Functional Programming in Haskell 1 / 56

A formal derivation of an executable Krivine machine

Programming with Math and Logic

HOL DEFINING HIGHER ORDER LOGIC LAST TIME ON HOL CONTENT. Slide 3. Slide 1. Slide 4. Slide 2 WHAT IS HIGHER ORDER LOGIC? 2 LAST TIME ON HOL 1

Induction and Semantics in Dafny

> module Lambda where > import Data.List -- I need some standard list functions

The Untyped Lambda Calculus

The Environment Model. Nate Foster Spring 2018

An experiment with variable binding, denotational semantics, and logical relations in Coq. Adam Chlipala University of California, Berkeley

Begin at the beginning

Intro. Scheme Basics. scm> 5 5. scm>

Harvard School of Engineering and Applied Sciences CS 152: Programming Languages

Transcription:

Lambda Calculus: Implementation Techniques and a Proof COS 441 Slides 15

Last Time: The Lambda Calculus A language of pure functions: values e ::= x \x.e e e v ::= \x.e With a call-by-value operational semantics: single step: (\x.e) v --> e [v/x] e1 --> e1 e1 e2 --> e1 e2 e2 --> e2 v e2 --> v e2 (beta) (app2) (app1) multi-step: e -->* e e1 --> e2 e2 -->* e3 e1 -->* e3 (reflexivity) (transitivity)

Examples We used the formal rules to build proofs that lambda terms could take steps: (beta) (\x.\y. x y) (\w.w) --> \y.(\w.w) y (app1) ((\x.\y. x y) (\w.w)) (\z.z) --> (\y.(\w.w) y) (\z.z) We showed it was possible to encode several simple kinds of data structures or computations booleans pairs numbers looping and I claimed you could code up anything else since the untyped lambda calculus is Turing-complete

IMPLEMENTING THE LAMBDA CALCULUS

Two Options First-order Abstract Syntax: build a data structure to represent a program data Lam = Var String Abs String Lam App Lam Lam Higher-order Abstract Syntax: use functions in Haskell to represent lambda calculus functions directly data Lam = Abs (Lam -> Lam) App Lam Lam FreeVar String

FIRST-ORDER SYNTAX

Data structure: Examples data Lam = Var String Abs String Lam App Lam Lam Examples: i = Abs "x" (Var "x") -- \x.x tru = Abs "t" (Abs "f" (Var "t")) fls = Abs "t" (Abs "f" (Var "f")) -- \t.\f.t -- \t.\f.f i, tru, fls all have type Lam

Substitution: Substitution -- subst e x v == e[v/x] -- v must be closed (no free variables)

Substitution: Substitution -- subst e x v == e[v/x] -- v must be closed (no free variables) subst (Var y) x v = if x == y then v else Var y

Substitution: Substitution -- subst e x v == e[v/x] -- v must be closed (no free variables) subst (Var y) x v subst (Abs y e) x v = if x == y then v else Var y = if x == y then (Abs y e) else (Abs y (subst e x v))

Substitution: Substitution -- subst e x v == e[v/x] -- v must be closed (no free variables) subst (Var y) x v = if x == y then v else Var y subst (Abs y e) x v = if x == y then (Abs y e) else (Abs y (subst e x v)) subst (App e1 e2) x v = App (subst e1 x v) (subst e2 x v)

Substitution: Substitution -- subst e x v == e[v/x] -- v must be closed (no free variables) subst (Var y) x v = if x == y then v else Var y subst (Abs y e) x v = if x == y then (Abs y e) else (Abs y (subst e x v)) subst (App e1 e2) x v = App (subst e1 x v) (subst e2 x v) Example: y = Var "y" x = Var "x" id = Abs "x" x foo = App (Abs "y" y) y subst foo "y" id == App (Abs "y" y) id

Values Code to determine if an expression is a value: -- is a value? -- value (Var s) = False value (Abs x e) = True value (App e1 e2) = False

Evaluation (\x.e) v --> e [v/x] (beta) eval :: Lam -> Lam e1 --> e1 e1 e2 --> e1 e2 (app1) -- beta rule eval (App (Abs x e) v) value v = subst e x v e2 --> e2 v e2 --> v e2 (app2)

Evaluation (\x.e) v --> e [v/x] (beta) eval :: Lam -> Lam e1 --> e1 e1 e2 --> e1 e2 (app1) -- beta rule eval (App (Abs x e) v) value v = subst e x v e2 --> e2 v e2 --> v e2 (app2) -- app2 rule eval (App v e2) value v = let e2' = eval e2 in App v e2'

Evaluation (\x.e) v --> e [v/x] (beta) eval :: Lam -> Lam e1 --> e1 e1 e2 --> e1 e2 (app1) -- beta rule eval (App (Abs x e) v) value v = subst e x v e2 --> e2 v e2 --> v e2 (app2) -- app2 rule eval (App v e2) -- app1 rule eval (App e1 e2) value v = let e2' = eval e2 in App v e2' = let e1' = eval e1 in App e1' e2

Evaluation (\x.e) v --> e [v/x] (beta) eval :: Lam -> Lam e1 --> e1 e1 e2 --> e1 e2 (app1) -- beta rule eval (App (Abs x e) v) value v = subst e x v e2 --> e2 v e2 --> v e2 (app2) -- app2 rule eval (App v e2) -- app1 rule eval (App e1 e2) value v = let e2' = eval e2 in App v e2' = let e1' = eval e1 in App e1' e2 -- forms that don't match LHS; no rule exists eval (Abs x e) = error "Value!" eval (Var x) = error "Stuck!"

HIGHER-ORDER SYNTAX

Higher-Order Abstract Syntax Key idea: use functions in Haskell to represent lambda calculus functions directly data Lam = Abs (Lam -> Lam) App Lam Lam FreeVar String needed for printing, and analysis of expressions not needed for evaluation expressions should not have free variables if you want to execute them

Higher-Order Abstract Syntax Key idea: use functions in Haskell to represent lambda calculus functions directly data Lam = Abs (Lam -> Lam) App Lam Lam remember, Abs is a converter: it takes a Lam -> Lam function and puts it in an "object" with 1 field that has type Lam Abs :: (Lam -> Lam) -> Lam App :: Lam -> Lam -> Lam

Higher-Order Abstract Syntax Key idea: use functions in Haskell to represent lambda calculus functions directly data Lam = Abs (Lam -> Lam) App Lam Lam f :: Lam -> Lam f = \x -> App x x e :: Lam e = Abs f Abs makes a Haskell function f into a Lam a Haskell variable with type Lam appears in the places a lambda calculus variable would think of the body of f like an expression with holes it: App [ ] [ ] during evaluation, we'll plug the holes with the argument to the function

Higher-Order Abstract Syntax Key idea: use functions in Haskell to represent lambda calculus functions directly f :: Lam -> Lam f = \x -> App x x e :: Lam e = Abs f data Lam = Abs (Lam -> Lam) App Lam Lam

Higher-Order Abstract Syntax Key idea: use functions in Haskell to represent lambda calculus functions directly f :: Lam -> Lam f = \x -> App x x e :: Lam e = Abs f data Lam = Abs (Lam -> Lam) App Lam Lam id :: Lam -> Lam id = \x -> x ide :: Lam ide = Abs id

Higher-Order Abstract Syntax Key idea: use functions in Haskell to represent lambda calculus functions directly f :: Lam -> Lam f = \x -> App x x e :: Lam e = Abs f id :: Lam -> Lam id = \x -> x ide :: Lam ide = Abs id e' :: Lam e' = App e ide data Lam = Abs (Lam -> Lam) App Lam Lam evaluating (App e ide): inside e, we have f = \x -> App x x applying f to ide, we get: App ide ide

Higher-Order Abstract Syntax Key idea: use functions in Haskell to represent lambda calculus functions directly id :: Lam -> Lam id = \x -> x ide :: Lam ide = Abs ide fls = = Abs (\t -> Abs (\f -> f)) tru = Abs (\t -> Abs (\f -> t)) An Alternative: id = \f -> f ide = Abs id flsf = \t -> Abs id fls = Abs flsf An Alternative: truf = \t -> Abs (\f -> t) tru = Abs truf

Evaluation eval :: Lam -> Lam eval (App (Abs f) v) value v = f v -- beta rule (\x.e) v --> e [v/x] (beta) eval (App v e2) value v = -- app2 rule let e2' = eval e2 in App v e2' eval (App e1 e2) = let e1' = eval e1 in App e1' e2 -- app1 rule e1 --> e1 e1 e2 --> e1 e2 e2 --> e2 v e2 --> v e2 (app2) (app1) eval (Abs f) = error "Value!" -- note: we never had to implement -- substitution ourselves; Haskell did it for us data Lam = Abs (Lam -> Lam) App Lam Lam

GETTING STUCK

Can Evaluation Ever Get Stuck? Values are lambda expressions that have properly finished being evaluated there is nothing more to do. In the pure lambda calculus, the only values are functions \x.x is a value. It can t be evaluated any further. \x.\y.x y is also a value Are there lambda terms that aren t values but can t be evaluated any further using the rules? If there were, we d call those things stuck expressions

Can Evaluation Ever Get Stuck? Values are lambda expressions that have properly finished being evaluated there is nothing more to do. In the pure lambda calculus, the only values are functions \x.x is a value. It can t be evaluated any further. \x.\y.x y is also a value Are there lambda terms that aren t values but can t be evaluated any further using the rules? If there were, we d call those things stuck expressions Expressions with free variables can be stuck! Eg: x x (\y.y) (\y. x y) (\w.w) isn t stuck right away, but will be after an evaluation step

Stuckness testing Given a lambda term, is it possible to create an automatic analyzer that decides, yes or no, whether or not a lambda term will ever get stuck?

Stuckness testing Given a lambda term, is it possible to create an automatic analyzer that decides, yes or no, whether or not a lambda term will ever get stuck? No! The lambda calculus is Turing-Complete. It can encode any Turing Machine. Suppose TM is a lambda term that simulates a Turing Machine Consider: (\x.y x) TM The above expression gets stuck by running in to free variable y if the TM halts; does not get stuck if the TM does not halt. We can t decide if TMs halt, so we can t decide if the lambda term ever gets stuck.

Stuckness testing Given a lambda term, is it possible to create an automatic analyzer that soundly but conservatively decides whether or not a lambda term will ever get stuck? ie: can we design an algorithm that given a lambda term, says no the lambda term is not stuck if it can guarantee the lambda term is not stuck? guarantee == sound says yes, maybe if it isn t sure? of course! the algorithm could always cop out and say yes, maybe But it turns out we can also define a principled, non-trivial analyzer that is sound and conservative, but for all practical purposes does a good enough job such an analyzer is called a scope checker and it is the simplest kind of type system

A SIMPLE SCOPE CHECKER

A Scope Checker for FOAS Expressions data Lam = Var String -- variables Abs String Lam -- \"x". e App Lam Lam -- e1 e2 closed :: Lam -> Bool closed e = clos [] e where clos env (Abs x e) = clos (x:env) e clos env (App e1 e2) = clos env e1 && clos env e2 clos env (Var x) = lookup env x lookup [] x = False lookup (y:env) x = x == y lookup env x

Scope Checking Examples A closed lambda expression: \y.\x.y is closed: closed (Abs "y" (Abs "x" (Var "y"))) == True y (\y.y) is not closed: closed (App (Var "y") (Abs "y" (Var y)) == False Can you come up with a lambda term that is not closed according to our Haskell definition but that evaluates safely without encountering a free variable? there must be one because I told you that it is undecidable whether execution encounters a free variable

Scope Checking Examples A closed lambda expression: \y.\x.y is closed: closed (Abs "y" (Abs "x" (Var "y"))) == True y (\y.y) is not closed: closed (App (Var "y") (Abs "y" (Var y)) == False Can you come up with a lambda term that is not closed according to our Haskell definition but that evaluates safely without encountering a free variable? there must be one because I told you that it is undecidable whether execution encounters a free variable (\x.\y.y) (\y.z) (\w.w) --> (\y.y) (\w.w) --> \w.w

A Scope Checker for HOAS Expressions module Lambda (Lam (Abs,App), -- only Abs App constructors useable by clients freevar, -- freevar function useable ) where data Lam = Abs (Lam -> Lam) App Lam Lam FreeVar String freevar :: String -> Lam freevar s = FreeVar ("!" ++ s)

A Scope Checker for HOAS Expressions module Lambda (Lam (Abs,App), -- only Abs App constructors useable by clients freevar, -- freevar function useable ) where data Lam = Abs (Lam -> Lam) App Lam Lam FreeVar String freevar :: String -> Lam freevar s = FreeVar ("!" ++ s) boundname = "bound" closed :: Lam -> Bool closed (Abs f) =

A Scope Checker for HOAS Expressions module Lambda (Lam (Abs,App), -- only Abs App constructors useable by clients freevar, -- freevar function useable ) where data Lam = Abs (Lam -> Lam) App Lam Lam FreeVar String freevar :: String -> Lam freevar s = FreeVar ("!" ++ s) boundname = "bound" closed :: Lam -> Bool closed (Abs f) = let body = f (FreeVar boundname) in closed body

A Scope Checker for HOAS Expressions module Lambda (Lam (Abs,App), -- only Abs App constructors useable by clients freevar, -- freevar function useable ) where data Lam = Abs (Lam -> Lam) App Lam Lam FreeVar String freevar :: String -> Lam freevar s = FreeVar ("!" ++ s) boundname = "bound" closed :: Lam -> Bool closed (Abs f) = let body = f (FreeVar boundname) in closed body closed (App e1 e2) = closed e1 && closed e2 closed (FreeVar s) = s == boundname

ONE MORE WAY TO DESCRIBE CLOSED EXPRESSIONS

Closed Expressions env ::= x1 : x2 :. : [] judgement form: clos env e -- "e has no free variables except those in env"

Closed Expressions env ::= x1 : x2 :. : [] judgement form: clos env e -- "e has no free variables except those in env" clos (x:env) e clos env \x.e -- if e is closed in (x:env) then \x.e is closed in env

Closed Expressions env ::= x1 : x2 :. : [] judgement form: clos env e -- "e has no free variables except those in env" clos (x:env) e clos env \x.e clos env e1 clos env e2 clos env (e1 e2) -- if e is closed in (x:env) then \x.e is closed in env -- if e1 and e2 are closed in env then e1 e2 is closed in env

Closed Expressions env ::= x1 : x2 :. : [] judgement form: clos env e -- "e has no free variables except those in env" clos (x:env) e clos env \x.e clos env e1 clos env e2 clos env (e1 e2) lookup env x == true clos env x -- if e is closed in (x:env) then \x.e is closed in env -- if e1 and e2 are closed in env then e1 e2 is closed in env -- if x is in env then x is closed in env

A PROOF

Evaluation Preserves Closedness Theorem: If clos [] e and e --> e' then clos [] e'.

Evaluation Preserves Closedness Theorem: If clos [] e and e --> e' then clos [] e'. Requires a lemma that substitution preserved Closedness Lemma: If clos [] (\x.e) and clos [] v then clos [] (e[v/x])

Evaluation Preserves Closedness Theorem: If clos [] e and e --> e' then clos [] e'. Proof: By induction on the derivation that e --> e' proofs by induction on the derivation of e --> e' have 1 case for each rule use the induction hypothesis on when subprog --> subprog in the premise of the rule. use lemma: If clos [] (\x.e) and clos [] v then clos [] (e[v/x])

Evaluation Preserves Closedness Theorem: If clos [] e and e --> e' then clos [] e'. Proof: By induction on the derivation that e --> e' Lemma: If clos [] (\x.e) and clos [] v then clos [] (e[v/x]) case: (\x.e) v --> e [v/x] (beta) (1) clos [] ((\x.e) v) (given) (2) clos [] (\x.e) (by 1, def of clos) (3) clos [] v (by 1, def of clos) (4) clos [] (e[v/x]) (by 2, 3) clos (x:env) e clos env \x.e clos env e1 clos env e2 clos env (e1 e2) lookup env x == true clos env x

Evaluation Preserves Closedness Theorem: If clos [] e and e --> e' then clos [] e'. Proof: By induction on the derivation that e --> e' Lemma: If clos [] (\x.e) and clos [] v then clos [] (e[v/x]) case: e1 --> e1 e1 e2 --> e1 e2 (app1) (1) clos [] (e1 e2) (given) (2) clos [] e1 (by 1, def of clos) (3) clos [] e2 (by 1, def of clos) (4) clos [] e1' (by IH, 2) (5) clos [] (e1' e2) (by 4, 3, def of clos) clos (x:env) e clos env \x.e clos env e1 clos env e2 clos env (e1 e2) lookup env x == true clos env x

Evaluation Preserves Closedness Theorem: If clos [] e and e --> e' then clos [] e'. Proof: By induction on the derivation that e --> e' Lemma: If clos [] (\x.e) and clos [] v then clos [] (e[v/x]) case: e2 --> e2 v e2 --> v e2 (app2) (1) clos [] (v e2) (given) (2) clos [] v (by 1, def of clos) (3) clos [] e2 (by 1, def of clos) (4) clos [] e2' (by IH, 3) (5) clos [] (v e2') (by 2, 4, def of clos) clos (x:env) e clos env \x.e clos env e1 clos env e2 clos env (e1 e2) lookup env x == true clos env x

Why do we care? Why do we care if closure is preserved by execution?

Why do we care? Why do we care if closure is preserved by execution? The initial motivation was that programs could get "stuck" when executing by running in to a free variable. We wanted to prevent that. In a real language implementations, getting "stuck" often means all hell breaks loose and random bad stuff ensues: derefencing a dangling pointer in C is another way to "get stuck" If we checked a program was closed, but then after 3 steps of evaluation a free variable appeared, then closure checking wouldn't be helpful -- it wouldn't prevent programs from getting stuck Moral: closure checking is a useful kind of static program analysis because if you check a program once before it executes, you never, ever have to worry about it getting stuck on a free variable, no matter how long it runs

SUMMARY

Summary There are at least two ways to implement the lambda calculus higher-order abstract syntax uses Haskell functions to implement lambdas and Haskell variables to implement lambda variables first-order abstract syntax uses strings to represent variables and does not use functions Unfortunate Fact: Almost every non-trivial property of how a lambda expression evaluates is undecidable Optimistic Perspective: We can approximate many properties Example: do we encounter a free var during execution: undecideable we can still design a useful scope checker the closure property is robust and highly useful because it is preserved by execution