Graphics by ewghwehws


									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
• 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

  159.331                      Prog Lang & Alg        8
1. One of the args is evaluated needlessly (makes code
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
     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
• 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
• 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
• 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
    159.331                  Prog Lang & Alg                         22
•   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

To top