From the λ-calculus to Functional Programming Drew McDermott Posted

Similar documents
Fundamentals and lambda calculus. Deian Stefan (adopted from my & Edward Yang s CSE242 slides)

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

λ calculus Function application Untyped λ-calculus - Basic Idea Terms, Variables, Syntax β reduction Advanced Formal Methods

Fundamentals and lambda calculus

Functional Programming

One of a number of approaches to a mathematical challenge at the time (1930): Constructibility

Introduction to Lambda Calculus. Lecture 5 CS 565 1/24/08

dynamically typed dynamically scoped

5. Introduction to the Lambda Calculus. Oscar Nierstrasz

Type Systems Winter Semester 2006

Functional Languages. Hwansoo Han

Programming Language Pragmatics

Introduction to the Lambda Calculus

Lambda Calculus. CS 550 Programming Languages Jeremy Johnson

Elixir, functional programming and the Lambda calculus.

1 Scope, Bound and Free Occurrences, Closed Terms

Introduction to Lambda Calculus. Lecture 7 CS /08/09

4/19/2018. Chapter 11 :: Functional Languages

Lambda Calculus. Lecture 4 CS /26/10

Chapter 5: The Untyped Lambda Calculus

Functional Programming and λ Calculus. Amey Karkare Dept of CSE, IIT Kanpur

Untyped Lambda Calculus

CSE 505: Concepts of Programming Languages

CMSC 330: Organization of Programming Languages

Lambda Calculus and Type Inference

Functional Programming. Pure Functional Languages

Lecture 5: The Untyped λ-calculus

Functional Programming. Pure Functional Languages

Lambda Calculus. Lambda Calculus

Chapter 11 :: Functional Languages

Untyped Lambda Calculus

Formal Semantics. Aspects to formalize. Lambda calculus. Approach

9/23/2014. Why study? Lambda calculus. Church Rosser theorem Completeness of Lambda Calculus: Turing Complete

Urmas Tamm Tartu 2013

CITS3211 FUNCTIONAL PROGRAMMING

CS61A Discussion Notes: Week 11: The Metacircular Evaluator By Greg Krimer, with slight modifications by Phoebus Chen (using notes from Todd Segal)

A Small Interpreted Language

Lambda Calculus and Type Inference

Introduction to the λ-calculus

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

CMSC 330: Organization of Programming Languages

COMP 1130 Lambda Calculus. based on slides by Jeff Foster, U Maryland

Lambda Calculus. Variables and Functions. cs3723 1

COP4020 Programming Languages. Functional Programming Prof. Robert van Engelen

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

VU Semantik von Programmiersprachen

(Refer Slide Time: 1:36)

Functional Languages. CSE 307 Principles of Programming Languages Stony Brook University

Functional Programming. Big Picture. Design of Programming Languages

Recursive Definitions, Fixed Points and the Combinator

Chapter 5: The Untyped Lambda Calculus

Activity. CSCI 334: Principles of Programming Languages. Lecture 4: Fundamentals II. What is computable? What is computable?

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

The Untyped Lambda Calculus

Programming Systems in Artificial Intelligence Functional Programming

Functional Programming

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.

Computer Science 203 Programming Languages Fall Lecture 10. Bindings, Procedures, Functions, Functional Programming, and the Lambda Calculus

(Refer Slide Time: 4:00)

CSCI B522 Lecture 11 Naming and Scope 8 Oct, 2009

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

The Lambda Calculus. notes by Don Blaheta. October 12, A little bondage is always a good thing. sk

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

1.3. Conditional expressions To express case distinctions like

// Body of shortestlists starts here y flatmap { case Nil => Nil case x :: xs => findshortest(list(x), xs)

The Untyped Lambda Calculus

CS 314 Principles of Programming Languages

Concepts of programming languages

Variables. Substitution

CS 11 Haskell track: lecture 1

CS 6110 S14 Lecture 1 Introduction 24 January 2014

Lecture 3: Recursion; Structural Induction

Lambda Calculus. Concepts in Programming Languages Recitation 6:

Programming Languages and Compilers (CS 421)

Tail Calls. CMSC 330: Organization of Programming Languages. Tail Recursion. Tail Recursion (cont d) Names and Binding. Tail Recursion (cont d)

INF 212 ANALYSIS OF PROG. LANGS LAMBDA CALCULUS. Instructors: Crista Lopes Copyright Instructors.

Programming Languages

CIS 500 Software Foundations Fall September 25

Formal Systems and their Applications

Introduction to lambda calculus Part 3

Lambda Calculus LC-1

More Untyped Lambda Calculus & Simply Typed Lambda Calculus

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

n Takes abstract syntax trees as input n In simple cases could be just strings n One procedure for each syntactic category

Lecture Notes on Data Representation

Lecture 7: Primitive Recursion is Turing Computable. Michael Beeson

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

Recursively Enumerable Languages, Turing Machines, and Decidability

Functional abstraction. What is abstraction? Eating apples. Readings: HtDP, sections Language level: Intermediate Student With Lambda

Functional abstraction

Pure Lambda Calculus. Lecture 17

Lambda Calculus and Extensions as Foundation of Functional Programming

Computer Science 21b (Spring Term, 2015) Structure and Interpretation of Computer Programs. Lexical addressing

J. Barkley Rosser, 81, a professor emeritus of mathematics and computer science at the University of Wisconsin who had served in government, died

CS 320 Midterm Exam. Fall 2018

Introduction 2 Lisp Part III

Introduction to the Lambda Calculus. Chris Lomont

Graphical Untyped Lambda Calculus Interactive Interpreter

Meta-programming with Names and Necessity p.1

Transcription:

From the λ-calculus to Functional Programming Drew McDermott drew.mcdermott@yale.edu 2015-09-28 Posted 2015-10-24 The λ-calculus was intended from its inception as a model of computation. It was used by Church to explore computability in the 1930s scooping Turing by about six months. 1 Once high-level programming languages were needed, language designers were naturally drawn to the λ-calculus, starting with McCarthy s use of LAMBDA in Lisp. An important issue in reasoning about the λ-calculus is which β-reduction to do next. This issue was explored by Alonzo Church and Barkley Rosser starting in the 1930s. They proved that the λ-calculus, even the untyped version, is confluent, meaning that if E 0 β E 1 and E 0 β E 1, then there is a term E 2 such that E 1 β E 2 and E 1 β E 2. 2 In other words, if you draw a directed graph of every formula you can reach from E 0 by a set of β-reductions, there are no dead ends, nor is there any branch point where reduction R takes you to one subgraph and R takes you to another, entirely disjoint subgraph. 3 Confluence does not mean that there are no infinite loops. In the untyped 1 Which is why the Church-Turing thesis is so called. However, other logicians found Turing s treatment more satisfying, because he had an explicit model of resource allocation : There s one symbol per tape square, and if the read/write head moves off the end of the tape, you go out and buy more. There s no limit to what you can record, as our civilization attests if we don t run out of paper. In Church s system, β-reduction requires you to copy arbitrarily large expressions to an arbitrary number of places, coming up with an arbitrary number of new variable names to avoid capturing free variables. As a primitive, it left something to be desired. Once you have the concept of Turing machine, you can easily produce a TM that evaluates an arbitrrary λ-calculus term, thus grounding Church s beautiful flights of fancy on actual pieces of paper. 2 What about all that variable renaming during β-reduction to avoid free-variable capture? Ah yes, that complication requires us to complicate the definition of confluence. There are various ways of doing it. The simplest way is probably to say There are terms E 2 and E 2 such that E 1 β E 2 and E 1 β E 2, and E 2 and E 2 are equivalent modulo bound-variable renaming. I.e., there s a one-to-one function between their variables that transforms one into the other, and doing so doesn t change which variable occurrences are bound where. This fine print takes all the excitement out of confluence. It makes one want to switch to a formalism that does without variables, which exist, but are harder for human readers to make sense of than even the λ-calculus. 3 Here s another way to fix the statement of confluence: Let s pretend we were talking about equivalence classes of formulas all along. That is, E 0, E 1, et al. are each an infinite set of formulas equivalent modulo renaming. (See above.) It s not hard to prove that if a β-reduction is possible for one member of such a set, it s possible for all, and the formulas you get are all in the same equivalence class. So we can just say E β F, where E and F are equivalence classes. Now the original statement of the confluence property is correct. 1

λ-calculus, there certainly are. Not only that, but strongly typed programming languages have to have notations for recursion and loops to ensure that they can compute anything computable, and this flexibility comes at the cost of tolerating infinite loops. In the λ-calculus, a computation comes to an end when a term is reached for which no β-reductions are possible. Recall that a redex is an occurrence of a β-convertible subterm in a term. So a term with no redexes is the terminus of a computation, a quiescent term. Another important theorem of Church and Rosser s is 4 that if there exists a series of β-conversions that will yield a quiescent term, then the following rule for picking the next redex will yield a quiescent term: Always pick the leftmost outermost redex. An outermost redex is one not contained inside another redex. Exercise: Prove to yourself that of two outermost redexes, one must lie entirely to the left of the other. Exercise: Prove to your roommate that confluence guarantees that if a λ-term can be reduced to two quiescent terms, then they are equivalent modulo renaming. If you write a programming-language interpreter, such as the classic meta-circular Lisp interpreter written in Lisp, you ll discover the ways in which such interpreters differ from Church & Rosser s scheme. Given a function expression to evaluate and nothing else, as in this snippet of Racket code: (cons (lambda (x) ((lambda (z) (list z z)) x)) ()) the result is a list with one element, that function of x. 5 It would be the rare meta-circular interpreter that did anything at all with the body of a λ-expression that wasn t applied to anything. But the λ-calculus equivalent: c(λx.(λz.l z z) x)n (c=cons, l=list, n = ()) has a perfectly good redex: (λz.l z z)x. β- converting it yields the quiescent term c(λx.l x x)n. 4 Insert careful bibliographic trace here. 5 If you don t know Lisp syntax, (cons e ()) makes a list of one element, the value of e. The point is that in the case at hand e is a lambda-expression, which is Racket syntax for a constant function. The Scala equivalent is ((x) => ((z) => List(z, z))(x)) :: Nil. I chose to use Racket here because it s so easy to write a meta-circular interpreter. If you ve never done it, you should find time to try. 2

Even more important is the fact that interpreters don t usually substitute the arguments of a function into the text of the function. 6 They keep track of variable-binding environments and closures. The details aren t important. Most programming languages violate the Church/Rosser redex-choice rule in other ways. Given a function and its arguments, they evaluate the arguments first, then pass the values to the function. 7 The name for this rule is call by value. The Church/Rosser rule would pass an argument in, and evaluate it when the parameter the argument was bound to was needed. What does it mean to pass an unevaluated argument? Think of it this way: If you allow functions of zero arguments, as most programming languages do, then you can get closer to the Church/Rosser rule by replacing every argument expression E with a zero-argument function (() => E), and replacing every occurrence of parameter p with p(). Take the classic example of trying to define ifnot c then e 1 else e 2 as a subroutine (it s supposed to evaluates e 1 only if c is false; if it s true, it evaluates e 2 ). That s because in calling ifnot(c, e 1, e 2 ), you ll evaluate all three expressions no matter what. We d like to define ifnot as ((c, e1, e2) => if (c) e2 else e1) If instead we define it as def ifnot(c, e1, e2) = if (c()) e2() else e1() (leaving types out of it) and call it by writing (e.g.) ifnot((() => n==0), (() => k/n), (() => 0)) then we ve approximated the Church/Rosser rule in a pure call-by-value language. This name for this behavior is call by name. (Presumably because every time the name of the parameter is encountered the function is called again, which amounts to evaluating the argument again.) This was the way arguments were passed in Algol 60, although it s hard to understand why. 8 A much better idea is to evaluate the argument the first time the parameter is referred to, and save the result; on subsequent 6 The only exceptions I know of are macro processors like TEX. 7 Of course, many languages are implemented using compilers rather than interpreters; for those languages, when I say language does X, I mean the compiler translates the program into code that does X. 8 Or even how. It was 1960, for crying out loud! [[Insert careful historical investigation here.]] 3

references the value is just retrieved. evaluation. 9 This is called call by need, or lazy Exercise: Much as we did for call by name, show how call by need could be implemented using functions and auxiliary variables in a callby-value language. In languages with call by name or by need, you can write ifnot as a function. That s because every function is compiled as if its arguments were implicit functions. Programming-language researchers call a function strict in its nth argument or in parameter p if it always evaluates its nth argument, or the argument p is bound to. It s strict (period) if it s strict in all its arguments. In a pure call-by-value language, every function is strict in all its arguments. Rather than insist that every function be strict or that no function be strict in any argument, languages often supply mechanisms for overriding the default. Haskell is lazy, but has notations indicating that a parameter is strict (i.e., that the function being defined is strict in that parameter). Scala is strict, but has notations indicating that a parameter is call-by-name, meaning that it will be re-evaluated every time the parameter is accessed, or never if the parameter is never accessed. Simply declare parameter p thus: p:=> T ; The notation looks like a function type with a defective argument type. It s not the same as p: ()=> T, which would require writing p(), just as in my trick for defining ifnot. You only have to write p, which is weirdly appropriate. Scala has another device, lazy variable bindings. If a val is prefixed with lazy, as in lazy val x = 1/y than 1/y is not evaluated until the first time x is evaluated. Note that this is true lazy evaluation. var y = 2.0 lazy val x = { val r = 1/y; y = y - 2.0; r} List(x,x) 9 The only justification I ve heard for true call by name is that, using side effects, you could alter the value of the parameter each time it was accessed. Thus you could build a weird kind of functional argument. (In dark corners of Scala, such as the Enumeration class, call-by-name parameters are used for just this purpose.) Passing actual functions makes much more sense, and is easier to keep under control. In a pure functional language, of course, it s impossible for a call-by-name argument to have a different value on different occasions. 4

works just fine; its value is List(0.5, 0.5). 5