Programming Languages: Chapter 8: Currying and Higher-order Functions



Variable-length argument lists in Scheme

Arguments to any function are always passed in as list. It is up to the programmer to decompose that argument list and group individual arguments in the formal parameter specification of the function definition using dot notation, if necessary.
    (define f (lambda (x) x))
    
    (f 1)
    (f '(1 2 3))
    
    ;;; x is just the list (1 2 3)
    (define f (lambda x x))
    
    (f 1 2 3)
    (f 1)
    
    ;;; uses pattern matching like ML and Haskell
    ;;; g and h take a variable number of arguments
    (define g (lambda (x . y) x))
    (define h (lambda (x . xs) xs))
    
    ;;; only 1 argument passed
    (g '(1 2 3))
    (h '(1 2 3))
    (write "a")
    (newline)
    
    ;;; now 2 arguments passed
    (g 1 '(2 3))
    (h 1 '(2 3))
    
    ;;; now 3 arguments passed
    (g 1 2 3)
    (h 1 2 3)
    


Kleene's Smn theorem

  • also known as the translation lemma, parameter theorem, or parameterization theorem from computability theory
  • formally, states that for any function f (x1, x2, ..., xn),

    f (a1, a2, ..., am) = g (xm+1, xm+2, ..., xn), where m < n, such that g (am+1, am+2, ..., an) = f (a1, a2, ..., am, am+1, am+2, ..., an).


Curried form in Haskell

    pow (0, _) = 1
    pow (1, b) = b
    pow (_, 0) = 0
    pow (e, b) = b * pow (e-1, b)
    
    powc 0 _ = 1
    powc 1 b = b
    powc _ 0 = 0
    powc e b = b * powc (e-1) b
    
    -- notice anything interesting?
    
    Main> :type pow
    pow :: (Num a, Num b) => (b,a) -> a
    
    Main> :type powc
    powc :: (Num a, Num b) => b -> a -> a
    
    square = powc 2
    cube = powc 3
    
    -- notice anything interesting?
    
    Prelude> :type map
    map :: (a -> b) -> [a] -> [b]
    Prelude> :type foldl
    foldl :: (a -> b -> a) -> a -> [b] -> a
    Prelude> :type foldr
    foldr :: (a -> b -> b) -> b -> [a] -> b
    Prelude> 
    
  • parenthesizing an operator converts it from an infix to prefix operator:
    -- called a 'section'
    inc = (+) 1
    inc1 = (+) 1.0
    
    Main> :type inc
    inc :: Integer -> Integer
    Main> :type inc1
    inc1 :: Double -> Double
    Main>
    
    -- [PIH] p. 118
    -- currying obviates the need for
    -- the helper function insertineach
    powerset [] = [[]]
    powerset (x:xs) =
       let
          temp = powerset xs
       in
          (map (x:) temp) ++ temp
    
    Main> :type powerset
    powerset :: [t] -> [[t]]
    
  • functions curry and uncurry
    Prelude> :type curry
    curry :: ((a, b) -> c) -> a -> b -> c
    
    Prelude> :type uncurry
    uncurry :: (a -> b -> c) -> (a, b) -> c
    


Curried form in ML

(same as in Haskell)
    fun pow 0 _ = 1
    |   pow 1 b = b
    |   pow _ 0 = 0
    |   pow e b = b * pow (e-1) b;
    
    (*
    int * int -> int
    =>
    int -> int -> int
     *)
    
    val square = pow 2;
    val cube = pow 3;
    
  • map, foldl, are foldr are defined in curried form:
    val ourimplode = foldr (op ^) "" o (map str);
    (* notice something peculiar about foldr and map *)
    
  • see [EMLP] Chapter 5 and, specifically § 5.5 (pp. 168-173), for more information.


Curried Form in Scheme

  • any language with first-class closures can be used to define functions in curried form
  • first attempt:
    ;;; here the curried form is too tightly woven into the function definition
    ;;; moreover, pow now cannot be completely evaluated
    (define pow
      (lambda (e)
        (cond
          ((eqv? e 0) 1)
          (else
           ;; return a closure
           (lambda (b)
             (cond
               ((eqv? b 0) 0)
               ((eqv? b 1) 1)
               ((eqv? e 1) b)
               (else (* b ((pow (- e 1)) b))))))))) 
    
    > (define square (pow 2))
    > (square 4)
    16
    
  • second attempt:
    (define s11
      (lambda (f x)
        (list 'lambda '(y) (list f x 'y))))
    
    (define pow
      (lambda (e b)
        (cond
          ((eqv? b 0) 0)
          ((eqv? b 1) 1)
          ((eqv? e 0) 1)
          ((eqv? e 1) b)
          (else (* b (pow (- e 1) b))))))
    
    > (define square (s11 pow 2))
    > square
    (lambda (y) (#<procedure:pow> 2 y))
    > (eval square)
    #<procedure>
    > ((eval square) 4)
    16
    
  • a similar approach:
    ;;; notice we must explicitly call papplyn which
    ;;; means we must know a-priori how many arguments are contained
    ;;; in the complete argument list of the function we desire to partially apply
    
    ;;; however, this approach obviates the need explicitly to call eval and
    ;;; the need define multiple versions of s (e.g., to partially apply
    ;;; a 3 argument function in all ways possible would require
    ;;; s111, s12, and s21).
    (define papplyn
      (lambda (fun . args)
        (lambda x
          (apply fun (append args x)))))
    
    (define square (papplyn pow 2))
    
    > (square 4)
    16
    
    ;; examples from Jeffrey A. Meunier's article ``Function Currying in Scheme''
    
    ;; and we can continue to partially apply the result as we desire
    (define list1 (papplyn list 'A))
    (define list2 (papplyn list1 'tree))
    (define list3 (papplyn list2 'grows))
    
    (list3 'in 'Brooklyn.)
    
    ;; since the original function (which is list)
    ;; can accept multiple arguments,
    ;; the partially applied function can also accept multiple arguments
    
    (define list4 (papplyn list 'A 'tree 'grows))
    (list4 'in 'Brooklyn.)
    


Partial evaluation

Generalizes the idea of currying from any prefix of the argument list to any subset of the argument list.

Formally, states that for any function f(x1, x2, ..., xn),

f(a, b, ..., r) = g({x1, x2, ..., xn} - {a, b, ..., r}), where {a, b, ..., r} ⊆ {x1, x2, ..., xn}, such that g(s, t, ..., z) = f(a, b, ..., z).


Haskell


    Mapping

      ourmap f [] = []
      ourmap f (x:xs) = (f x):(ourmap f xs)
      
      square n = n*n
      
      ans = ourmap square [1,2,3,4,5,6]
      
      squarelist lon = map square lon
      
      ans2 = squarelist [1,2,3,4,5,6]
      
      vs.
      
      squarelist = map square
      


    Functional composition

      Haskell's composition operator is .
      add3 x = x+3
      mult2 x = x*2
      
      add3_then_mult2 = mult2 . add3
      mult2_then_add3 = add3 . mult2
      
      ans3 = add3_then_mult2 3
      
      ans4 = mult2_then_add3 3
      


    Fooooolding lists

      use foldr and foldl

      best to think of foldl and foldr non-recursively [PIH] pp. 65 & 68
      -- foldl and foldr take a prefix binary function,
      -- a base value of recursion, and a list, in that order
      
      -- foldl :: (a -> b -> a) -> a -> [b] -> a
      -- foldl associates from the left:
      -- +(+(+(+(0,1),2),3),4) = 10
      -- think of foldl as using the accumulator approach
      ans5 = foldl (+) 0 [1,2,3,4]
      
      -- -(-(-(-(0,1),2),3),4) = (((0-1)-2)-3)-4 = -10
      ans6 = foldl (-) 0 [1,2,3,4]
      
      -- foldr :: (a -> b -> b) -> b -> [a] -> b
      -- foldr associates from the right:
      -- (1 :: (2 :: (3 :: [])))
      -- (1 - (2 - (3 - (4 - 0)))) = -2
      ans7 = foldr (-) 0 [1,2,3,4]
      
      -- for reasons of efficiency, use foldl
      -- rather than foldr when they produce the
      -- same result
      
      sumlist = foldl (+) 0
      
      -- ref. [PIH] pp. 65-66
      length1 = foldr (\_ n -> n+1) 0
      
      -- cons reversed
      snoc x xs = xs ++ [x]
      
      reverse6a [] = []
      reverse6a (x:xs) = snoc x (reverse6a xs)
      
      reverse6 = foldr snoc []
      
      -- ref. [PIH] p. 148
      reverse7 = foldl (\xs x -> x : xs) []
      


    Putting it all together: higher-order functions

    Use these concepts (higher-order functions) to define a function string2int which converts a string representation of an integer to the corresponding integer:
      Main> :type string2int
      string2int :: [Char] -> Int
      Main> string2int "0"
      0
      Main> string2int "123"
      123
      Main> string2int "321"
      321
      Main> string2int "5643452"
      5643452
      
    Hint: only requires 2 lines of code, or only 1 if you use a literal function (see below).
      import Data.Char (ord)
      
      char2int sumsofar initChar  = (ord initChar) - (ord '0') + 10*sumsofar
      
      string2int l = foldl char2int 0 l
      
      string2int' = foldl (\r c -> (ord c) - (ord '0') + 10*r) 0
      
      char2int' initChar sumsofar  = (ord initChar) - (ord '0') + 10*sumsofar
      
      -- not recommended, why? hint: two reasons
      string2int'' s = foldr char2int' 0 (reverse s)
      


    Overloading

      contrast square function in Haskell with that in ML
      square n = n*n
      
      Main> :type square
      square :: Num a => a -> a
      -- the type of square is a 'qualified type'
      -- and Num is a 'type class'
      


    Summary

    (ref. [WFPM])

    Higher-order functions (allows functions to be glued together) and lazy evaluation (allows whole programs to be glued together) are the glue which allows us to combine program components together in creative ways to produce concise, malleable, and reusable programs and, thereby enables modular programming which makes programs easier to debug, maintain, and re-use.


ML


    Mapping

      fun ourmap f nil = nil
      | ourmap f (x::xs) = (f x)::ourmap f xs;
      
      fun square x = x*x;
      
      ourmap square [1,2,3,4,5,6];
      
      fun squarelist lon = map square lon;
      
      squarelist [1,2,3,4,5,6];
      
      vs.
      
      val squarelist = map square;
      


    Functional composition

      ML's composition operator o
      fun add3 x = x+3;
      fun mult2 x = x*2;
      
      val add3_then_mult2 = mult2 o add3;
      val mult2_then_add3 = add3 o mult2;
      
      add3_then_mult2 3;
      (* val it = 12 : int *)
      
      mult2_then_add3 3;
      (* val it = 9 : int *)
      
      val add3_then_mult2 = (op o) (mult2, add3);
      
      (* op converts an infix operator to a prefix operator *)
      (* be careful *)
      (op *) (4, 5); (* doesn't work *)
      (op * ) (4, 5); (* now it works *)
      
      


    Fooooolding lists

      use foldr and foldl
      (* foldl and foldr take a prefix binary function,
         a base value of recursion, and a list, in that order *)
      
      (* the standard ML foldl seems to have the wrong type!
         foldl : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b
      
         foldl associates from the left (e.g.,
         +(+(+(+(0,1),2),3),4) = 10)
         think of foldl as using the accumulator approach *)
      foldl (op +) 0 [1,2,3,4];
      
      (* -(4, -(3, -(2, -(1,0)))) = 4-(3-(2-(1-0))) = 2 *)
      foldl (op -) 0 [1,2,3,4];
      
      (* foldr : ('a * 'b -> 'b) -> 'b -> 'a list -> 'b
         foldr associates from the right (e.g.,
         (1 :: (2 :: (3 :: []))))
         (1 - (2 - (3 - (4 - 0)))) = ~2 *)
      foldr (op -) 0 [1,2,3,4];
      
      val sumlist = foldr (op +) 0;
      


    Putting it all together: higher-order functions

    • now we use these concepts to define our own implode function
      implode (explode ("apple"));
      (* val it = "apple" : string *)
      val exploded = explode ("apple");
      (* val exploded = [#"a",#"p",#"p",#"l",#"e"] : char list *)
      val apple = implode ([#"a",#"p",#"p",#"l",#"e"]);
      (* val apple = "apple" : string *)
      
      (* first attempt at implementing our own version of implode *)
      val combine x = foldr (op ^) #"";
      
      why will this not work?
      (* str converts from char to string *)
      (* char list -> string *)
      val ourimplode = (foldr op ^ "") o (map str); (* this works *)
      
    • converting a string representing an integer to an integer
      (* example: 123 = (3+0) + (2*10) + (1*100) *)
      fun char2int (c, v) = ord (c) - ord (#"0") + 10*v;
      (* val char2int = fn : char * int -> int *)
      
      (* char2int (#"3", char2int (#"2", char2int (#"1", 0))); *)
      foldl char2int 0 (explode ("123"));
      (* val it = 123 : int *)
      
      fun string2int s = foldl char2int 0 (explode s);
      
      fun string2int1 s = foldl (fn (c, v) => ord (c) - ord (#"0") + 10*v) 0 (explode s);
      


References

    [EMLP] J.D. Ullman. Elements of ML Programming. Prentice Hall, Upper Saddle River, NJ, Second edition, 1997.
    [PIH] G. Hutton. Programming in Haskell. Cambridge University Press, Cambridge, 2007.

Return Home