1 / 23

159.331 Programming Languages & Algorithms

159.331 Programming Languages & Algorithms. Lecture 20 - Functional Programming Languages - Part 2 Higher-Order Functions, Currying, Lazy Evaluation, Equations and Pattern Matching. Higher-Order Functions.

karif
Download Presentation

159.331 Programming Languages & Algorithms

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 159.331 Programming Languages & Algorithms Lecture 20 - Functional Programming Languages - Part 2 Higher-Order Functions, Currying, Lazy Evaluation, Equations and Pattern Matching Prog Lang & Alg

  2. 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. Prog Lang & Alg

  3. 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! Prog Lang & Alg

  4. 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 Prog Lang & Alg

  5. 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 Prog Lang & Alg

  6. 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 Prog Lang & Alg

  7. 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… Prog Lang & Alg

  8. 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? Prog Lang & Alg

  9. One of the args is evaluated needlessly (makes code slower) • 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 Prog Lang & Alg

  10. 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… Prog Lang & Alg

  11. Prog Lang & Alg

  12. 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 Prog Lang & Alg

  13. 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..] Prog Lang & Alg

  14. 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 Prog Lang & Alg

  15. 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 Prog Lang & Alg

  16. 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++ Prog Lang & Alg

  17. 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 onpatternswith 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 Prog Lang & Alg

  18. 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 Prog Lang & Alg

  19. 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 Prog Lang & Alg

  20. 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 Prog Lang & Alg

  21. 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 Prog Lang & Alg

  22. 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. Prog Lang & Alg

  23. 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. Summary Prog Lang & Alg

More Related