570 likes | 653 Views
Learn about functional programming, including concepts, features, and implementations of languages like Standard ML and Haskell. Explore topics such as computation, type systems, lazy evaluation, and data structures.
E N D
Functional languages and how to implement them Karl-Filip Faxén KTH/IMIT/LECS
Functional programming • A functional program computes a result from a set of arguments • compute a target program from a source program • compute a bitmap image from a scene description • A function (subprogram) is a function (mapping) • computes a result from arguments with • no side effects! • Well known functional languages include • Standard ML (not purely functional) • Haskell (purely functional)
Functional languages • Garbage collection • Polymorphic typing • Catches all type errors at compile time • Types are inferred and need not be given • Polymorphism allows generic functions to be written • First class functions • Allows for higher order functions • Eager (strict) or lazy (nonstrict) evaluation • Standard ML is strict • Haskell is nonstrict
Strict or nonstrict? let z = x/y in if y == 0 then 1 else z What happens if y=0?
Strict or nonstrict? let z = x/y in if y == 0 then 1 else z What happens if y=0? - In a strict language (SML): a run-time error - In a lazy language (Haskell): return the value 1
Strict or nonstrict? if y == 0 then 1 else x/y What happens if y=0?
Strict or nonstrict? if y == 0 then 1 else x/y What happens if y=0? - In a strict language (SML): return the value 1 - In a lazy language (Haskell): return the value 1
Data structures data List a = Nil | Cons a (List a)
Data structures data List a = Nil | Cons a (List a) Defines a new type name List ...
Data structures data List a = Nil | Cons a (List a) Defines a new type name List ... and two constructors, Nil and Cons.
Data structures data List a = Nil | Cons a (List a) The type List is parameterized by the type parameter a which represents the type of the list elements (think of templates in C++)
Data structures data List a = Nil | Cons a (List a) The constructor Nil has no arguments ...
Data structures data List a = Nil | Cons a (List a) The constructor Nil has no arguments ... but the constructor Cons has two ...
Data structures data List a = Nil | Cons a (List a) The constructor Nil has no arguments ... but the constructor Cons has two ... the first is a list element ...
Data structures data List a = Nil | Cons a (List a) The constructor Nil has no arguments ... but the constructor Cons has two ... the first is a list element ... and the second is the rest of the list.
Data structures data List a = Nil | Cons a (List a) Some examples of lists: Cons 1 (Cons 2 (Cons 3 Nil)) :: List Int Cons True (Cons False (Cons False Nil)) :: List Bool
Data structures data List a = Nil | Cons a (List a) Some examples of lists: Cons 1 (Cons 2 (Cons 3 Nil)) :: List Int Cons True (Cons False (Cons False Nil)) :: List Bool The type of the list elements instantiate the type parameter of List
Data structures data List a = Nil | Cons a (List a) Some examples of lists: Cons 1 (Cons 2 (Cons 3 Nil)) :: List Int Cons True (Cons False (Cons False Nil)) :: List Bool The type of the list elements instantiate the type parameter of List
Computing with data structures take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil Defines the function take; take n xs returns the first n elements of xs or xs if xs is shorter than n
Computing with data structures take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil Definition by pattern matching; a kind of implicit case statement. The first equation that matches is selected.
Computing with data structures take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil A literal pattern matches if the argument is that literal (in this case 0) ...
Computing with data structures take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil A literal pattern matches if the argument is that literal (in this case 0) ... a variable pattern matches any value (and binds the variable to it) ...
Computing with data structures take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil A literal pattern matches if the argument is that literal (in this case 0) ... a variable pattern matches any value (and binds the variable to it) ... so the first equation is chosen if the first argument is 0
Computing with data structures take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil A constructor pattern matches if ...
Computing with data structures take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil A constructor pattern matches if ... the argument is built with the same constructor ...
Computing with data structures take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil A constructor pattern matches if ... the argument is built with the same constructor ... and the component patterns match the values of the components of the argument ...
Computing with data structures • take 0 xs = Nil • take n (Cons y ys) = Cons y (take (n-1) xs) • take n Nil = Nil • A constructor pattern matches if ... • the argument is built with the same constructor ... • and the component patterns match the values of the components • of the argument ... • so the second equation is chosen if • the first argument is not zero, and • the second argument is not the empty list
Computing with data structures • take 0 xs = Nil • take n (Cons y ys) = Cons y (take (n-1) xs) • take n Nil = Nil • The function take is polymorphic; its type is • take :: Int -> List a -> List a • since the list elements are only extracted from and stored in • data structures
Higher order functions filter p (Cons x xs) = if p(x) then Cons x (filter p xs) else filter p xs filter p Nil = Nil The parameter p is a function ...
Higher order functions filter p (Cons x xs) = if p(x) then Cons x (filter p xs) else filter p xs filter p Nil = Nil The parameter p is a function ... and filter p xs returns the elements of xs for which p returns True
Lazy evaluation sieve (Cons x xs) = Cons x (sieve (filter g xs)) where g y = remainder y x /= 0 from n = Cons n (from (n+1)) primes n = take n (sieve (from 2)) Compute the first n primes as the first n elements of the (infinite) list of all primes
Lazy evaluation sieve (Cons x xs) = Cons x (sieve (filter g xs)) where g y = remainder y x /= 0 from n = Cons n (from (n+1)) primes n = take n (sieve (from 2)) Compute the first n primes as the first n elements of the (infinite) list of all primes
Lazy evaluation sieve (Cons x xs) = Cons x (sieve (filter g xs)) where g y = remainder y x /= 0 from n = Cons n (from (n+1)) primes n = take n (sieve (from 2)) Compute the first n primes as the first n elements of the (infinite) list of all primes
Representing data structures Cons descriptor Cons node Header list element next element GC info Constructor #2 Constructor # Nil 1 Cons 2 Header Value (e.g. 1) In the stack or In the heap or In the text segment in registers in the data segment
Representing functions sieve (Cons x xs) = Cons x (sieve (filter g xs)) where g y = remainder y x /= 0 The function g is passed to filter; how is it represented?
Representing functions sieve (Cons x xs) = Cons x (sieve (filter g xs)) where g y = remainder y x /= 0 The variable x is free in the body of g, so just a code pointer is not enough!
Representing functions Function descriptor Function node Header value of x GC info Code for the body of g This pointer is passed to filter Header Value (e.g. 1)
Implementing lazy evaluation from n = Cons n (from (n+1)) The function from produces its result one element at a time (Just In Time evaluation!)
Implementing lazy evaluation from n = Cons n (from (n+1)) The suspended computation contains the free variable n
Implementing lazy evaluation Thunk descriptor Thunk node Header value of n GC info Code for evaluating the expression from (n+1) This pointer is stored in the Cons node Header Value (e.g. 1)
Implementing lazy evaluation from n = Cons n (thunk from (thunk (eval n)+1)) Thunks and evals can be made explicit in an intermediate language for lazy evaluation (Faxén -95, ...)
Why lazy functional programs are (sometimes) slow • There are overheads from some features • Extra cost for building thunks which are eventually evaluated (most thunks are) • Evaluating a thunk is expensive since it involves an indirect call (bad for optimization and pipelines) • Polymorphism and GC needs uniform representations; thunks force this representation to be boxed • Difficult to make array operations efficient
Why lazy functional programs are (sometimes) slow • An inefficient programming style is encouraged • Many small functions • Frequent use of higher order functions • Linked data structures rather than monolithic ones • Many intermediate data structures • But this style is good for programmers!
Optimizing functional programs • Standard optimizations • Inlining, constant propagation and folding, etc • Optimizations of lazy evaluation • Strictness analysis • Cheap eagerness (Faxén -95, -00, -02) • Sharing analysis (to avoid updates) (Faxén -97) • Representation analysis (Faxén -99) • Deforestation (fusion) • Cloning (Faxén -01)
Strictness analysis take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil The function take is strict in its first argument since it is necessary to know its value in order to produce any output ...
Strictness analysis take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil The function take is strict in its first argument since it is necessary to know its value in order to produce any output ... because it is needed for pattern matching
Strictness analysis take 0 xs = Nil take n (Cons y ys) = Cons y (take (n-1) xs) take n Nil = Nil The function take is not strict in its second argument since it is not necessary to know its value if the first argument is 0
Cheap eagerness from n = Cons n (thunk from (thunk (eval n)+1)) primes n = take n (thunk sieve (from 2)) The function from is not strict since it can produce a Cons node without using its argument ...
Cheap eagerness from n = Cons n (thunk from (thunk (eval n)+1)) primes n = take n (thunk sieve (from 2)) The function from is not strict since it can produce a Cons node without using its argument ... so the thunk can not be eliminated by strictness analysis
Cheap eagerness from n = Cons n (thunk from (thunk (eval n)+1)) primes n = take n (thunk sieve (from 2)) Only the eval is expensive in the thunk body ...