VIEWS: 5 PAGES: 23 POSTED ON: 8/24/2012 Public Domain
159.331 Programming Languages & Algorithms Lecture 18 - Functional Programming Languages - Part 2 Higher-Order Functions, Currying, Lazy Evaluation, Equations and Pattern Matching 159.331 Prog Lang & Alg 1 Higher-Order Functions • Many (most) imperative languages treat variables and functions differently (non-orthogonality again!) • Variables can be manipulated (read, written, passed around) but functions are “second-class citizens” and can only be invoked • Functional languages usually treat functions as first-class objects, which can be passed around as arguments, returned as a function result or stored in a data structure • A function that takes another function as an argument is called a higher-order function • This idea is a cornerstone of functional programming. 159.331 Prog Lang & Alg 2 • An important higher-order function is map, which takes a function and a list as input and returns a new list with the function applied to each element map triple [1,2,3,4] => [triple 1, triple 2, triple 3, triple 4 ] => [3, 6, 9, 12] • Note we need the capability of passing a function (in this case “triple”) as an argument to map • We could of course write a special-case function such as map_triple, but this has to have a name of its own and is no longer general - we want our concepts to be reuseable! • Having the higher-order function capability a language is powerful - useful if it is not the ugly and hard-to-remember function-pointer syntax that C and C++offer! 159.331 Prog Lang & Alg 3 • Functional languages usually offer other built in higher- order functions • Fold-right or foldr is used to compute a single result (eg a sum or product) from a list foldr (+) 0 [2, 4, 7] is equal to: ( 2 + ( 4 + ( 7 + 0 ) ) ) => 13 • This is Miranda syntax where if an infix operator like “+” is passed as an argument it must be in parentheses. • foldr (*) 1 x computes the product of all elements in list x • foldr (&) True x computes logical “and” of all the elements in x • The built-in higher-order functions are type checked 159.331 Prog Lang & Alg 4 Currying • Most functional languages support currying or partial parameterization (named after the logician H.B.Curry) mult a b = a * b • Is a function which if applied to two arguments will compute their product • In Miranda we can apply it to just one argument, and the result is another function which takes one argument, so we can define triple function as: triple = mult 3 • We say that mult has been “curried” or “partially parameterised” - its need for args has been partially met 159.331 Prog Lang & Alg 5 • We can define a function to sum a list: sumlist = foldr (+) 0 • This has provided 2 out of the 3 arguments required by foldr so the result is a function that takes one argument (a list) hence: sumlist [ 5, 9, 20] => 34 • The type of function mult is: mult :: num -> ( num -> num ) • Which means mult can be regarded as a function with one argument that returns another function of type num -> num 159.331 Prog Lang & Alg 6 Lazy Evaluation • In evaluating a function the simplest way is to first evaluate all the arguments then invoke the function. • eg in: mult ( fac 3 ) ( fac 4 ) • The function applications (fac 3) and (fac 4) would be done first in arbitrary order, reducing the expression to mult 6 24 • Then mult is applied to its two arguments resulting in 144 • This is known as applicative order reduction • It starts with the inner-most expressions • Sometimes we do not want to do it this way… 159.331 Prog Lang & Alg 7 • In Lazy evaluation we start with the outermost expression and work inwards, not evaluating sub expressions until we need their results: • So: mult (fac 3) (fac 4) is reduced to: (fac 3) * (fac 4) • and next to 6 * 24 • and finally 144 • Consider a function cond that is like the ternary operator ? : in C/C++ cond b x y = x, if b cond b x y = y, otherwise • What happens if we applicatively evaluate the three arguments? 159.331 Prog Lang & Alg 8 1. One of the args is evaluated needlessly (makes code slower) 2. If the arg that is evaluated needlessly never terminates, then the whole expression never terminates • Often we use this construct to guard against some non- terminating or incorrect condition • In functional languages we might want to define the factorial function: fac n = cond (n=0) 1 (n * fac (n -1) ) • If the third argument is always evaluated this will never terminate (fac 1 invokes fac 0 which invokes fac -1 …. • These problems have led to the concept of lazy evaluation 159.331 Prog Lang & Alg 9 • Lazy evaluation uses normal order reduction and is where the arguments of a function are only evaluated when their values are needed • Contrast this with applicative order reduction which is also known as eager evaluation • Using lazy evaluation, the following figure shows how the expression is reduced… 159.331 Prog Lang & Alg 10 159.331 Prog Lang & Alg 11 Strict Semantics • A language based on eager evaluation is said to have strict semantics because it always evaluates the arguments of functions • A language using lazy evaluation is said to have non-strict semantics • Miranda has non-strict semantics so fac in the example evaluates correctly 159.331 Prog Lang & Alg 12 • Lazy evaluation is also useful for defining and manipulating (potentially) infinite data structures like open-ended lists or sets and sequences (as compared with an array which is finitely bounded) • In Miranda [1..] denotes the (infinite) list of all positive integers, and can be manipulated: hd [1..] => 1 || 1st element hd ( tl [1..] ) => 2 || 2nd element hd ( tl ( map triple [1..] )) => 6 || 2nd element of || tripled list • This works because Miranda will only build the finite part of the list that is actually needed. Only if we (foolishly) request the whole list will the system run out of memory or never terminate eg sumlist [1..] or #[1..] 159.331 Prog Lang & Alg 13 I/O in Functional Languages • I/O is a problem as it is not referentially transparent in functional languages • Address this problem by modeling I/O with lazy evaluation • A functional program takes a list of responses from the operating system (OS) as input • It produces a list of requests to the OS where the n’th response answers the n’th request • Reading and writing files can be modeled as data requests and confirmation exchanges with the OS • With lazy semantics the program can look at the OS responses when it wants to • Request and response lists are just streams of messages 159.331 Prog Lang & Alg 14 • Lazy evaluation semantics are costly to implement (surprise, NOT!) • Normal order reduction may evaluate an expression more than once • Applicative order evaluates expressions exactly once • double x = x + x • Normal order evaluates double 23 * 45 as: double 23 * 45 => 23 * 45 + 23*45 => 1035+23*45 => 1035 + 1035 => 2070 • Whereas applicative order evaluates more efficiently as: • double 23 * 45 => double 1035 = 1035 + 1035 => 2070 159.331 Prog Lang & Alg 15 Graph Reduction • Graph reduction is a technique for solving this inefficiency problem of lazy evaluation of expressions • It allows us to specify we want expressions evaluates zero or one times but not multiple times - adds some complexity to the compiler • There is also a small overhead in postponing evaluation but this can be optimised out by the compiler • Not all languages provide lazy evaluation • Languages that do not will have some feature that does provide lazy semantics eg the if-statement and its variants in imperative languages - recall also the short-cut logical operators in C/C++ 159.331 Prog Lang & Alg 16 Equations and Pattern Matching • Modern functional languages like Miranda or Haskell have a capability for specifying equational functions (or just “equations”) • We have seen examples like if x>0…otherwise… • This idea generalises to allow choices based on patterns with syntax like: <pattern> = <expression>, condition • Where the condition part is optional • The pattern can use formal parameters etc • If a function is invoked the system tries to find a match 159.331 Prog Lang & Alg 17 • In Miranda (and most cases of this) it is the first textually occuring match that succeeds that will be chosen • Our cond example becomes the set of equations: cond True x y = x cond False x y = y • And fac can be written as: fac 0 = 1 fac (n+1) = (n+1) * fac n • 1st pattern matches if the argument is equal to zero and more subtly the second pattern matches only if the argument is greater than or equal to 1 159.331 Prog Lang & Alg 18 • In general a numerical expression appearing ina pattern may have the form v + c where v is a variable and c is a literal constant • The pattern only matches if v can be set to a non-negative value • More general numerical expressions are disallowed (to prevent ambiguities) • For example f(n+m) = n *m is ambiguous and hence illegal - a call like f 9 could result in 1*8 or 2*7 or other combinations 159.331 Prog Lang & Alg 19 • Patterns can also contain lists • We can implement the length (of a list) function: length [ ] = 0 length ( a : b) = 1 + (length b) • Pattern in 1st equation specifies an empty list, second will only match if argument is a non-empty list, and then makes a recursive call passing the tail of the list as the new argument • Recall : is cons operator inserts element onto front of a list 159.331 Prog Lang & Alg 20 • Function uniq accepts a sorted list and eliminates all but the first occurrence of each item uniq [ ] = [ ] ||escape hatch: empty list uniq ( a: ( a: x) ) = uniq ( a : x ) || matches eg [3,3,…] uniq ( a : x ) = a : uniq x || matches all other lists • 2nd equation uses an advanced feature - that of specifying the same variable (a) twice in the same pattern so this matches eg [3,3,4] but not [3,4,5] • If 2nd succeeds a recursive call is made with same list as argument but with head omitted • If first two fail third is used also with recursive call but head element intact 159.331 Prog Lang & Alg 21 • Note that textual order of equations is significant • (in uniq example 2nd and 3rd can succeed simultaneously) • May be preferable to add some condition to the third pattern requiring its first two elements to be different - matter of programming style • Note also that uniq is polymorphic: uniq [ 3, 3, 4, 6, 6, 6, 6, 7 ] => [ 3, 4, 6, 7] uniq [‘a’, ‘b’, ‘b’, ‘c’ ] => [‘a’, ‘b’, ‘c’ ] • This approach is typical of functional programming. 159.331 Prog Lang & Alg 22 Summary • Higher-Order functions • Currying • Strict semantics • Equations and pattern matching • See Bal & Grune Chapter 4, Sebesta Chapter 15 • Next - application examples, and examples of functional languages and summary. 159.331 Prog Lang & Alg 23