CSC 347 - Concepts of Programming Languages

Folds

Instructor: Stefan Mitsch

Learning Objectives

How to combine collection elements into an aggregate result?

  • Understand folds

Exercise: Sum the Elements of a List

Express in an imperative style

Java

int sum (List<Int> xs) {
  int result = 0;
  for (int i = 0; i < xs.length; i++) 
    result += xs.get(i);
  return result;
}

Scala

def sum (xs:List[Int]) : Int = 
  var result = 0
  for i <- 0 until xs.length do 
    result = result + xs(i)

Exercise: Sum the Elements of a List

Express in a functional style

def sum (xs:List[Int])            : Int =  xs match 
  case Nil   => 0
  case y::ys => y + sum (ys)

val xs = List(11,21,31)
sum (xs)
sum(11::21::31::Nil)
--> sum(11::21::31::Nil)
--> 11 + sum(21::31::Nil)
--> 11 + (21 + sum(31::Nil))
--> 11 + (21 + (31 + sum(Nil)))
--> 11 + (21 + (31 + 0))
--> 11 + (21 + 31)
--> 11 + 52
--> 63 = (11 + (21 + (31 + 0)))

Exercise: Sum the Elements of a List

With a different zero element

def sum (xs:List[Int], z:Int = 0) : Int =  xs match 
  case Nil   => z
  case y::ys => y + sum (ys, z)

val xs = List(11,21,31)
sum (xs)
sum(11::21::31::Nil)
--> sum(11::21::31::Nil, 0)
--> 11 + sum(21::31::Nil, 0)
--> 11 + (21 + sum(31::Nil, 0))
--> 11 + (21 + (31 + sum(Nil, 0)))
--> 11 + (21 + (31 + 0))
--> 11 + (21 + 31)
--> 11 + 52
--> 63 = (11 + (21 + (31 + 0)))

Exercise: Sum the Elements of a List

Sum of elements in a list computing forward

def sum (xs:List[Int], z:Int = 0) : Int =  xs match 
  case Nil   => z
  case y::ys => sum (ys, z + y)

val xs = List(11,21,31)
sum (xs)
sum(11::21::31::Nil)
--> sum(11::21::31::Nil, 0)
--> sum(21::31::Nil, 11)
--> sum(31::Nil, 32)
--> sum(Nil, 63)
-->
-->
-->
--> 63 = (((0 + 11) + 21) + 31)

Folds

generalize the + operation

def sum       (xs:List[Int], z:Int)                     : Int =  
  xs match 
    case Nil   => z
    case y::ys => sum       (ys, z + y)

val xs = List(11,21,31)
sum       (xs, 0)

Folds

generalize the + operation

def foldLeft  (xs:List[Int], z:Int, f:((Int,Int)=>Int)) : Int = 
  xs match 
    case Nil   => z
    case y::ys => foldLeft  (ys, f(z,y), f)

val xs = List(11,21,31)
foldLeft  (xs, 0, _+_)

Folds

Change the return type

def foldLeft  (xs:List[Int], z:String, f:(String,Int)=>String) : String = 
  xs match 
    case Nil   => z
    case y::ys => foldLeft  (ys, f(z, y), f)

val xs = List(11,21,31)
foldLeft  (xs, "", _ + " " + _)

Folds

Changing the parameter type

def foldLeft  (xs:List[List[Int]], z:Int, f:(Int,List[Int])=>Int) : Int = 
  xs match 
    case Nil   => z
    case y::ys => foldLeft  (ys, f(z, y), f)

val xss = List(List(11,21,31),List(),List(41,51))
foldLeft  (xss, 0, _ + _.length)

Folds

Abstracting the type

def foldLeft  [Z,X] (xs:List[X], z:Z, f:((Z,X)=>Z)) : Z =
  xs match 
    case Nil   => z
    case y::ys => foldLeft  (ys, f(z,y), f)

val xs = List(11,21,31)
foldLeft  (xs, "!", (z:String,x:Int) => z + " " + x)
res1: String = ! 11 21 31

Fold Left vs. Fold Right

Fold Left

def foldLeft  [Z,X] (xs:List[X], z:Z, f:((Z,X)=>Z)) : Z =
  xs match 
    case Nil   => z
    case y::ys => foldLeft  (ys, f(z,y), f)

val xs = List(11,21,31)
foldLeft  (xs, "!", (z:String,x:Int) => z + " " + x)
res1: String = ! 11 21 31

Fold Right

def foldRight [X,Z] (xs:List[X], z:Z, f:((X,Z)=>Z)) : Z =
  xs match 
    case Nil   => z
    case y::ys => f (y, foldRight (ys, z, f))

val xs = List(11,21,31)
foldRight (xs, "!", (x:Int,z:String) => x + " " + z)
res1: String = 11 21 31 !

Folds Builtin in Lists

  • Scala List class has fold methods (curried!)
    xss.foldLeft (0) ((z,xs)=>z + xs.length)
    

Fold Left vs. Fold Right

def foldLeft [Z,X] (xs:List[X], z:Z, f:((Z,X)=>Z)) : Z =  xs match {
  case Nil   => z
  case y::ys => foldLeft  (ys, f(z,y), f)
}
def foldRight [X,Z] (xs:List[X], z:Z, f:((X,Z)=>Z)) : Z =  xs match {
  case Nil   => z
  case y::ys => f (y, foldRight (ys, z, f))
}
  • foldLeft is tail recursive: return foldLeft (ys, f(z, y))
    • apply f to the head and the accumulated result
    • recursive call on the tail
    • base case used with first element
  • foldRight is recursive into an argument:
    • return f (y, foldRight (ys, z))
    • recursive call on the tail
    • apply f to the head and result of recursion
    • base case used with last element

Fold Left vs. Fold Right

def foldLeft [Z,X] (xs:List[X], z:Z, f:((Z,X)=>Z)) : Z =  xs match 
  case Nil   => z
  case y::ys => foldLeft (ys, f(z,y), f)
def foldRight [X,Z] (xs:List[X], z:Z, f:((X,Z)=>Z)) : Z =  xs match 
  case Nil   => z
  case y::ys => f (y, foldRight (ys, z, f))
val xs = List(a, b, c)
foldLeft (xs, z, f) === f( f( f(z,a),b),c)
foldRight(xs, z, f) === f(a, f(b, f(c,z)))
               xs.foldLeft(z)(f)       xs.foldRight(z)(f)
                      f                       f
                     / \                     / \
                    f   c                   a   f
                   / \                         / \
                  f   b                       b   f
                 / \                             / \
                z   a                           c   z

Folds are Universal

def sum         (xs: List[Int])              = xs.foldLeft(0)(_+_)
def prod        (xs: List[Int])              = xs.foldLeft(1)(_*_)
def or          (xs: List[Boolean])          = xs.foldLeft(false)(_||_)
def and         (xs: List[Boolean])          = xs.foldLeft(true)(_&&_)
def append  [X] (xs: List[X])(ys: List[X])   = xs.foldRight(ys)(_::_)
def flatten [X] (xs: List[List[X]])          = xs.foldLeft(Nil:List[X])(_:::_)
def length  [X] (xs: List[X])                = xs.foldLeft(0)((z,x)=>z+1)
def reverse [X] (xs: List[X])                = xs.foldRight(Nil:List[X])((x,zs)=>zs:::List(x))
def map   [X,Y] (xs: List[X], f: X=>Y)       = xs.foldRight(Nil:List[Y])(f(_)::_)
def filter  [X] (xs: List[X], f: X=>Boolean) = xs.foldRight(Nil:List[X])((x,zs)=>if f(x) then x::zs else zs)

Summary

  • Folds are universal functions to combine list elements into an aggregate result
  • foldRight folds from the right (zero element combined with last element)
  • foldLeft folds from the left (zero element combined with list head)