CS 474: Object-Oriented
Languages and Environments
Lecture 10: Functional Data
Structures. Laziness. Exceptions Suck!
Instructor: Dr. Mark Grechanik
University of Illinois at Chicago
List: Functional Data Structure
Department of Computer Science, the
University of Illinois at Chicago 2
No Redundant Copying Is Needed
n Constructing List objects
q val ex1: List[Double] = Nil
q val ex2: List[Int] = Cons(1, Nil)
q val ex3: List[String] = Cons("a", Cons("b", Nil))
Department of Computer Science, the
University of Illinois at Chicago 3
Pattern Matching
n Exploit the pattern in construction of functional
data structures
q def sum(ints: List[Int]): Int = ints match {
case Nil => 0
case Cons(x,xs) => x + sum(xs)}
q def product(ds: List[Double]): Double = ds match {
case Nil => 1.0
case Cons(0.0, _) => 0.0
case Cons(x,xs) =>
x * product(xs)
}
Department of Computer Science, the
University of Illinois at Chicago 4
Data Sharing in Functional Structures
n When we add an element 1 to the front of an existing list, say xs, we return
a new list, in this case Cons(1,xs).
n Since lists are immutable, we don’t need to actually copy xs; we can just
reuse it.
n This is called
Department of Computer Science, the
University of Illinois at Chicago 5
Do You Notice Repeated Code?
n Exploit the pattern in construction of functional
data structures
q def sum(ints: List[Int]): Int = ints match {
case Nil => 0
case Cons(x,xs) => x + sum(xs)}
q def product(ds: List[Double]): Double = ds match {
case Nil => 1.0
case Cons(0.0, _) => 0.0
case Cons(x,xs) =>
x * product(xs)
}
Department of Computer Science, the
University of Illinois at Chicago 6
Do You Notice Repeated Code?
n Exploit the pattern in construction of functional
data structures
q def sum(ints: List[Int]): Int = ints match {
case Nil => 0
case Cons(x,xs) => x + sum(xs)}
q def product(ds: List[Double]): Double = ds match {
case Nil => 1.0
case Cons(0.0, _) => 0.0
case Cons(x,xs) =>
x * product(xs)
}
Department of Computer Science, the
University of Illinois at Chicago 7
Use Lambda Functions!
n Whenever you encounter duplication like this,
you can generalize it away by pulling
subexpressions out into function arguments.
n If a subexpression refers to any local
variables (the + operation refers to the local
variables x and xs introduced by the pattern,
similarly for product), turn the subexpression
into a function that accepts these variables as
arguments.
Department of Computer Science, the
University of Illinois at Chicago 8
Fold It!
Department of Computer Science, the
University of Illinois at Chicago 9
Understanding the Fold Function
T accumulator = initialValue;
for(X listItem : list){
acc = method(acc, listItem);
}
return acc;
val list = List(1,2,3)
list.foldLeft(0)(_ + _)
Department of Computer Science, the
University of Illinois at Chicago 10
Understanding the Fold Function
List(1,2,3).foldLeft(0)(_ + _)
Department of Computer Science, the
University of Illinois at Chicago 11
Doing Things With Folds
n def sum(list: List[Int]): Int = list.foldLeft(0)((r,c) => r+c)
n def product(list: List[Int]): Int = list.foldLeft(1)(_*_)
n def count(list: List[Any]): Int = list.foldLeft(0)((sum,_) => sum + 1)
n def average(list: List[Double]): Double = ???
n def last[A](list: List[A]): A =
n def penultimate[A](list: List[A]): A =
list.foldLeft( (list.head, list.tail.head) )((r, c) => (r._2, c) )._1
n def contains[A](list: List[A], item: A): Boolean =
list.foldLeft(false)(_ || _==item)
n def reverse[A](list: List[A]):List[A] = list.foldLeft(List[A]())((r,c)=> c :: r)
n def unique[A](list: List[A]): List[A] = list.foldLeft(List[A]()) { (r,c) =>
if (r.contains(c)) r else c :: r }.reverse
https://oldfashionedsoftware.com/2009/07/30/lots-and-lots-of-foldleft-examples/
Department of Computer Science, the
University of Illinois at Chicago 12
Exercises
n Write a function map that generalizes modifying each
element in a list while maintaining the structure of the list
q def map[A,B](as: List[A])(f: A => B): List[B]
n Write a function filter that removes elements from a list
unless they satisfy a given predicate.
q filter[A](as: List[A])(f: A => Boolean): List[A]
n Write a function flatMap that works like map except that
the function given will return a list instead of a single
result, and that list should be inserted into the final
resulting list.
q def flatMap[A,B](as: List[A])(f: A => List[B]): List[B]
Department of Computer Science, the
University of Illinois at Chicago 13
Execution Trace: Laziness
n List(1,2,3,4).map(_ + 10).filter(_ % 2 == 0).map(_ * 3)
n List(11,12,13,14).filter(_ % 2 == 0).map(_ * 3)
n List(12,14).map(_ * 3)
n List(36,42)
n To say a function is non-strict just means that the function may
choose not to evaluate one or more of its arguments.
n In contrast, a strict function always evaluates its arguments.
n The type () => A is a syntactic alias for the type Function0[A]
Department of Computer Science, the
University of Illinois at Chicago 14
Lazy List
Department of Computer Science, the
University of Illinois at Chicago 15
Revisiting Exceptions for FuncADT
Department of Computer Science, the
University of Illinois at Chicago 16
Revisiting Exceptions for FuncADT
Department of Computer Science, the
University of Illinois at Chicago 17
Referential Opaqueness of Exceptions
n The meaning of RT expressions does not depend on
context and may be reasoned about locally
n The meaning of non-RT expressions is context-
dependent and requires more global reasoning.
n For instance, the meaning of the RT expression 42 + 5
doesn’t depend on the larger expression it’s embedded
in—it’s always and forever equal to 47.
n But the meaning of the expression throw new
Exception("fail") is very context-dependent, it takes on
different meanings depending on which try block (if any)
it’s nested within.
Department of Computer Science, the
University of Illinois at Chicago 18
Exceptions Suck!
n Exceptions break RT and introduce context dependence,
moving us away from the simple reasoning of the
substitution model and making it possible to write
confusing exception-based code.
q This is the source of the folklore advice that exceptions should be
used only for error handling, not for control flow.
n Exceptions are not type-safe. The type of failing
Fn:Int => Int tells us nothing about the fact that
exceptions may occur, and the compiler will certainly not
force callers of failing Fn to make a decision about how
to handle those exceptions.
n If we forget to check for an exception in failingFn, this
won’t be detected until runtime.
Department of Computer Science, the
University of Illinois at Chicago 19
Revisiting Exceptions for FuncADT
Department of Computer Science, the
University of Illinois at Chicago 20
What About Checked Exceptions?
n Java’s checked exceptions don’t work for higher-order
functions, which can’t possibly be aware of the specific
exceptions that could be raised by their arguments.
n def map[A,B](l: List[A])(f: A => B): List[B]
n Can we have a version of map for every single checked
exception that could possibly be thrown by f.
n Even if we wanted to do this, how would map even know
what exceptions were possible?
n This is why generic code, even in Java, so often resorts
to using RuntimeException or some common checked
Exception type.
Department of Computer Science, the
University of Illinois at Chicago 21
What Should We Do?
n Ignore the complaints. Stick with Exceptions.
n Instead of throwing an exception, we return a
value indicating that an exceptional condition
has occurred.
n What value? Zero? -1? null?
n What if those values conflict with the values
produced by computations in programs?
n Maybe error values should belong to some
special types that mean exactly this – error!
Department of Computer Science, the
University of Illinois at Chicago 22
Return Some Value of Type Double
n It allows errors to silently propagate—the caller can forget to check
this condition and won’t be alerted by the compiler
n It results in a fair amount of boilerplate code at call sites, with explicit
if statements to check whether the caller has received a “real” result
n It’s not applicable to polymorphic code
n It demands a special policy or calling convention of callers
Department of Computer Science, the
University of Illinois at Chicago 23
The Data Type Option
n sealed trait Option[+A]
case class Some[+A](get: A) extends Option[A]
case object None extends Option[Nothing]
n Option can be defined as Some, or it can be
undefined as None.
n def mean(xs: Seq[Double]): Option[Double] =
if (xs.isEmpty) None
else Some(xs.sum / xs.length)
Department of Computer Science, the
University of Illinois at Chicago 24
The Data Type Option
Department of Computer Science, the
University of Illinois at Chicago 25
Option As a List
n Option can be thought of like a List that can
contain at most one element
Department of Computer Science, the
University of Illinois at Chicago 26
Using Option
Department of Computer Science, the
University of Illinois at Chicago 27
But… Current Code Is Optionless L
n Callers of methods that take or return Option
will have to be modified to handle either
Some or None.
n We can lift ordinary functions to become
functions that operate on Option.
q def lift[A,B](f: A => B): Option[A] =>Option[B] = _ map f
q val absO: Option[Double] => Option[Double] = lift(math.abs)
n def insuranceRateQuote(age: Int,
numberOfSpeedingTickets: Int): Double
Department of Computer Science, the
University of Illinois at Chicago 28
Option: Lifting Functions
Department of Computer Science, the
University of Illinois at Chicago 29
Exceptions => Some or None
Department of Computer Science, the
University of Illinois at Chicago 30
Data Type Either
n Option doesn’t tell us anything about what
went wrong in the case of an exceptional
condition.
n sealed trait Either[+E, +A]
case class Left[+E](value: E) extends Either[E, Nothing]
case class Right[+A](value: A) extends Either[Nothing, A]
n Either is a disjoint union of two types.
n The Right constructor is reserved for the
success case and Left is used for failure.
Department of Computer Science, the
University of Illinois at Chicago 31
Using Either
n def mean(xs: IndexedSeq[Double]): Either[String, Double] = {
if (xs.isEmpty)Left("mean of empty list!")
else Right(xs.sum / xs.length) }
n def Try[A](a: => A): Either[Exception, A] = {
try Right(a) catch { case e: Exception => Left(e) } }
n def safeDiv(x: Int, y: Int): Either[Exception, Int] = {
try Right(x / y) catch { case e: Exception => Left(e) } }
Department of Computer Science, the
University of Illinois at Chicago 32
Main Functional Programming Goal
n Separate program description from its evaluation!
n First-class functions capture some computation in their
bodies but only execute it once they receive their
arguments.
n Option captures the fact that an error occurred, but the
decision of what to do about it becomes a separate
concern.
n Also, we need build up a computation that produces a
sequence of elements without running the steps of that
computation until those elements are actually needed.
Department of Computer Science, the
University of Illinois at Chicago 33
List Execution Trace
n List(1,2,3,4).map(_ + 10).filter(_ % 2 == 0).map(_ * 3)
n List(11,12,13,14).filter(_ % 2 == 0).map(_ * 3)
n List(12,14).map(_ * 3)
n List(36,42)
n To say a function is non-strict just means that the function may
choose not to evaluate one or more of its arguments.
n In contrast, a strict function always evaluates its arguments.
n The type () => A is a syntactic alias for the type Function0[A]
Department of Computer Science, the
University of Illinois at Chicago 34
Stream Execution Trace
Department of Computer Science, the
University of Illinois at Chicago 35
Infinite Streams
val ones: Stream[Int] = Stream.cons(1, ones)
Department of Computer Science, the
University of Illinois at Chicago 36
Reading
n Mandatory
q Functional Programming in Scala by
Paul Chiusano and Rúnar Bjarnason
Published by Manning Publications,
2014
n Chapters 3, 4, and 5
q Homework
q Q/A on Teams
q Get ready for the final exam
Department of Computer Science, the
University of Illinois at Chicago