Lecture 10: Functional Programming Languages Programming Languages Course Aarne Ranta (aarne@chalmers.se) %!target:html %!Encoding:utf-8 %!postproc(html): #NEW %!postproc(html): #HR
%!postproc(html): #sub1 1 %!postproc(html): #subn1 n-1 %!postproc(html): #subn n Book: 6.5, 7.3 #NEW ==Plan== Programming with states vs. values Functions as values Anonymous functions Lambda calculus Evaluation strategies Simply typed lambda calculus Polymorphic functions and principal types #NEW ==Imperative vs. functional programming== Two **programming language paradigms**: **Imperative** (aka. procedural): the execution of a program is both evaluation of expressions and changing a state (values of variables). - "stateful" programming - evaluation can have side effects **Functional**: the execution of a program is just evaluation of expressions, without changing the values of variables. - "stateless" programming - no side effects States and side effects have a double effect: + enable efficient time and space consumption + make it harder to reason about the program and therefore to optimize it In the past, (1) dominated. Currently (2) is getting more important. In fact, a powerful optimization technique is **single static assignments**, where all variables are only given value once. #NEW ==The structure of a functional program== The levels are: - modules - definitions - expressions Thus no statements! Example (could be written in a subset of Haskell): ``` doub x = x + x twice f x = f (f x) quadruple = twice doub main = print (twice quadruple 2) ``` #NEW ==Functions in imperative languages== So, is functional programming less expressive than imperative? Sure we can write functions in C/C++/Java, too: ``` // doub x = x + x int doub (int x) { return x + x ; } ``` But this is what is called a **first-order function**: the arguments are objects like numbers and class objects - but not themselves functions. In C++, it is also possible to write **second-order functions**, which take functions as arguments: ``` // twice f x = f (f x) int twice(int f (int n), int x) { return f(f(x)) ; } ``` #NEW ==Functions as first-class citizens== In a functional language, a functions are **first-class citizens**. This means: a function has a value even if it is not applied. This is not quite so in C++: you cannot return a function as a value: ``` // quadruple = twice doub /* not possible (int f (int x)) quadruple(int x) { return twice(doub) ; } */ int quadruple(int x) { return twice(doub, x) ; } ``` #NEW ==Function types and partial application== The types of functions in Haskell are written in this way: ``` max : Int -> Int -> Int ``` The notation is right-associative, and hence equivalent to ``` max : Int -> (Int -> Int) ``` Thus, a "two-place function" is really a one-place function whose value is also a function. (Haskell uses ``::`` for typing, but we stick to ``:``) The typing rule for functions is: ``` Env => f : A -> B Env => a : A --------------------------------- Env => f a : B ``` Thus **partial application** is meaningful: ``` max 4 :: Int -> Int -- returns the greater one of its argument and 4 ``` #NEW ==Functions of tuples, currying== In many other languages, the value of a function must be a "basic type", i.e. not a function type. This corresponds in Haskell to having a **tuple** of arguments: ``` maxt : (Int,Int) -> Int ``` The typing rule for tuples is ``` Env => a : A Env => b : B --------------------------- Env => (a,b) : (A,B) ``` Partial application is thus not meaningful: you have to form the tuple first. The move ``` (A,B) -> C ==> A -> B -> C ``` is called **currying**, with reference to Haskell B. Curry. At the same time as it is a powerful programming technique, it simplifies the semantics and implementation of programming languages. #NEW ==Anonymous functions== In a functional language, you can form **anonymous functions** by using **lambda abstraction**: ``` timesNine = twice (\x -> x + x + x) ``` In C++, you would have to write a named function for tripling: ``` // triple x = x + x + x int triple(int x) { return x + x + x ; } // timesNine = twice triple int timesNine(int x) { return twice(triple, x) ; } ``` There is a recent [Lambda Library http://www.boost.org/doc/libs/1_38_0/doc/html/lambda.html] for C++ permitting this. #NEW ==Desugaring function definitions== There is no difference in writing a function definition in these three ways: ``` foo x y = x + y * y foo x = \y -> x + y * y foo = \x -> \y -> x + y * y ``` We can thus simplify the abstract syntax of programs by saying that definitions have the form ``` Def ::= Ident "=" Exp ``` and treating the other form ``` Def ::= Ident [Ident] "=" Exp ``` as syntactic sugar: ``` id x#sub1 ... x#subn = exp ===> id = \x#sub1 -> ... \x#subn -> exp ``` In this way, the environment is very simple: just mapping identifiers to expressions! #NEW ==Evaluating expressions== Now it is enough to work with the following tiny language: ``` Exp ::= Ident | Integer | "(" Exp Exp ")" | "(" "\" Ident "->" Exp ")" ``` Thus the expressions are variables, integer constants, applications, and lambda abstractions. (Operations like ``+`` can be treated as functions.) For simplicity, we define the values to be expressions (notice that values are either integers or functions). The big-step semantics is: ``` env => var ⇩ lookup env var env => const ⇩ const env => fun ⇩ (\x -> body) env => arg ⇩ val env => body(val/x) ⇩ result -------------------------------------------------------------------------------- env => (fun arg) ⇩ result env => (\x -> body) ⇩ (\x -> body) ``` Thus lambda abstractions are not evaluated when alone, but only when they are applied to arguments. The operation ``body(val/x)`` is **substitution**: replace all occurrences of the variable ``x`` by the expression ``val`` in the expression ``body``. #NEW ==Substitution== Example: ``` (x + x + 3)(5/x) ⇩ 5 + 5 + 3 ``` Definition, by syntax-directed translation ``` const(val/x) = const var(val/x) = val, if var == x var(val/x) = var, if var != x (fun arg)(val/x) = (fun(val/x) arg(val/x)) (\z -> body)(val/x) = (\z -> body(val/x)), if no capture ``` For abstraction, we need a condition to prevent **capture**. Capture means that some ``z`` in ``val`` becomes bound by the lambda. Example evaluation: ``` twice = \f -> \a -> f (f a) double = \x -> x + x ((twice double) 3) = (((\f -> \a -> f (f a)) (\x -> x + x)) 3) = (((\a -> (\x -> x + x) ((\x -> x + x) a))) 3) = (((\a -> (\x -> x + x) (a + a))) 3) = (((\a -> a + a + a + a)) 3) = 3 + 3 + 3 + 3 ``` #NEW ==Examples of capture== Recall the unconditioned substitution rule: ``` (\z -> body)(val/x) = (\z -> body(val/x)) ``` The following go wrong: ``` (\x -> x)(5/x) = (\x -> x(5/x)) -- should not replace a bound x = (\x -> 5) (\y -> x)(y/x) = (\y -> x(y/x)) -- should not bind a free y = (\y -> y) ``` Solution: **alpha conversion**: change the name of a bound variable to a new one that does not appear in the substitution. ``` (\x -> x)(5/x) = (\z -> z)(5/x) = (\z -> z(5/x)) = (\z -> z) (\y -> x)(y/x) = (\z -> x)(y/x) = (\z -> x(y/x)) = (\z -> y) ``` #NEW ==Call by value vs. call by name== Two **evaluation strategies**. **Call by value** (as above): evaluate argument before substitution ``` env => fun ⇩ (\x -> body) env => arg ⇩ val env => body(val/x) ⇩ result ---------------------------------------------------------------------------- env => (fun arg) ⇩ result ``` **Call by name** : substitute first, then evaluate ``` env => fun ⇩ (\x -> body) env => body(arg/x) ⇩ result -------------------------------------------------------- env => (fun arg) ⇩ result ``` What difference does it make? - with call by value, all arguments are always evaluated - with call by name, some arguments are never evaluated C, C++, Java, ML use call by value. Haskell uses a variant of call by name: call by need. #NEW ==Termination of evaluation== Consider the code ``` infinite = 1 + infinite first x y = x main = first 5 infinite ``` With call by value, we get ``` main = first 5 infinite = (\x -> \y -> x) 5 (1 + infinite) = (\y -> 5) (1 + infinite) = (\y -> 5) (2 + infinite) ... ``` With call by name, ``` main = first 5 infinite = (\x -> \y -> x) 5 infinite = (\y -> 5) infinite = 5 ``` Generally: if an evaluation can terminate, it terminates with the call by name order. But not necessarily with call by value. #NEW ==Lazy evaluation and call by need== Call by name is sometimes called **lazy evaluation**: don't evaluate until you need! Disadvantage: you may need to evaluate the same expression twice: ``` doub x = x + x doub (doub 8) = doub 8 + doub 8 -- by name = 8 + 8 + 8 + 8 = 32 doub (doub 8) = doub 16 -- by value = 16 + 16 = 32 ``` **Call by need**: change the environment by filling in the value when an expression is evaluated. Next time, the value is found from the environment and not computed again. #NEW ==Types for lambda calculus== In **simply typed lambda calculus**, there is - a set of basic types, such as ``int`` - function types of form ``typ1 -> typ2`` The typing rules are ``` env => var : A, if A == lookup env var env => const : int, for integer constants env => fun : A -> B env => arg : A ------------------------------------- env => (fun arg) : B env, x : A => body : B ---------------------------- env => (\x -> body) : A -> B ``` where ``A, B`` are any types #NEW ==Polymorphism== What is the type of ``` \x -> x ``` There are infinitely many types: ``` int -> int (int -> int) -> (int -> int) (int -> int - int) -> (int -> int -> int) ``` All these types have the form ``` A -> A ``` Conversely, what ever type ``A`` is, the expression has it. We say that ``\x -> x`` is **polymorphic**: it has many types. #NEW ==Polymorphism in C++ and Java== Some polymorphism is possible in C++ using **templates**. ``` // twice : (A -> A) -> A -> A template A twice(A f (A n), A x) { return f(f(x)) ; } ``` Similarly in generic Java (Java 1.5): ``` // id : A -> A public static A id(A x) { return x ; } ``` (and there are no functions of functions). Templates in C++ and generics in Java were inspired by ML and Haskell. #NEW ==Type inference in functional languages== In general, the result is polymorphic. There is an algorithm returning **the most general type**. Thus ``` infer (\x -> x) = A -> A infer (\x -> \y -> x) = A -> B -> A infer (\f -> \x -> f (f x)) = (A -> A) -> A -> A infer (\x -> x + x) = int -> int ``` #NEW ==An example of type inference== ``` infer (\f -> \x -> f (f x)) : t t = a -> b -> c ==> f (f x) : c f : d -> e ==> a = d -> e because f : a c = e because f _ : e f x : d because f : d -> _ f x : e because f : _ -> e hence d = e x : d ==> d = b because x : b we have a = d -> e = b -> b c = d = b hence t = (b -> b) -> b -> b ``` Isn't this mind-twisting - a little bit like Sudoku? Can it be made systematic? #NEW ==Implementing type inference== The algorithm calls **unification**: Book 6.5.4 Unification finds solutions to equations with type variables. Unification algorithm: Book 6.5.5 #NEW ==Polymorphic typing rules== ``` env => var : A', if A == lookup env var and A > A' env => const : int, for integer constants env => fun : A -> B env => arg : A ------------------------------------- env => (fun arg) : B env, x : A => body : B ---------------------------- env => (\x -> body) : A -> B ``` Here ``A > A'`` means that ``A'`` is an **instance** of ``A``. Examples: ``` a > Int a > Int -> Int a > b -> c a -> b > Int -> (Int -> b) ``` where ``a, b, c`` are type variables. #NEW ==The unification algorithm== Input: two types with variables. Output: **type substitution** //s// (mapping from variables to types). Notation: //Ts// is type //T// after performing substitution //s//. Property: if //s// = unify //T U//, then //Ts// = //Us// ``` unify a b = {a = b} -- a,b distinct variables unify a T = {a = T} -- T non-variable, a doesn't occur in T unify (T -> U) (T' -> U') = s1 := unify T T' s2 := unify Us1 U's1 return s2 + s1 ``` **Occurs check**: we cannot unify e.g. ``a`` with ``a -> b``. #NEW ==Type inference with unification== Input: environment mapping variables to their types, expression. Output: substitution, type. ``` infer env x = ({}, fresh env (lookup x env)) infer env n = ({}, Int) infer env (fun arg) = (s1,T1) := infer env fun (s2,T2) := infer (env s1) arg b := freshVar env s3 := unify (T1 s2) (T2 -> b) return (s3 + s2 + s1, b s3) infer env (\x -> body) = b := freshVar env (s,T) := infer (env, x : b) body return (s, (b s) -> T) ``` The auxiliary ``freshVar`` returns a type variable not yet occurring in env. #NEW ==Type inference example revisited== ``` infer () (\f -> \x -> f (f x)) infer (f : a, x : b) (f (f x)) ({},c) <- infer (f:a, x:b) f ({},d) <- infer (f:a, x:b) x {c = d -> e} <- unify c (d -> e) ({c = d -> e}, e) <- infer env (f x) {c = e -> e} <- unify c (e -> e) ({c = d -> e, c = e -> e, d = e}, e) <- infer env (f (f x)) (subst, e -> e) <- infer env (\x -> f (f x)) (subst, (e -> e) -> e -> e) <- infer env (\f -> \x -> f (f x)) ``` with some shortcuts and "obvious" abbreviations env and subst... it's better to implement and try out!