Slides for my recent presentation at the CASE meetup, May 21st. Discusses functional programming features in Scala. Goes from basic FP features like higher-order functions all the way through to monads.
2. whoami
Author of Scala for Java Refugees
and other articles on Scala and FP
Former editor Javalobby / EclipseZone
Engaged in academic research
involving Scala DSLs and text parsing
(ScalaBison, GLL
Combinators, ScalaQL)
3. Agenda
Define “functional programming” (sort
of)
See some common elements of FP
Motivate why this stuff is useful in the
real world (hopefully)
Show practical functional techniques
and design patterns
Explain monads!
Hopefully pique your interest in
learning and applying more of this
7. Definitions
Q: What is “purely-functional”?
◦ Everything is immutable (no variables)
8. Definitions
Q: What is “purely-functional”?
◦ Everything is immutable (no variables)
◦ Absolutely no side-effects
println(quot;Hello, World!quot;)
9. Definitions
Q: What is “purely-functional”?
◦ Everything is immutable (no variables)
◦ Absolutely no side-effects
◦ Referential transparency
10. Definitions
Q: What is “purely-functional”?
◦ Everything is immutable (no variables)
◦ Absolutely no side-effects
◦ Referential transparency
◦ Bondage discipline?
11. Definitions
Scala is not purely-functional
◦ vars
◦ Mutable collections
◦ Uncontrolled side-effects (println)
12. Definitions
Scala is not purely-functional
◦ vars
◦ Mutable collections
◦ Uncontrolled side-effects (println)
Is Scala a “functional language”?
16. Functional Trademarks
Higher-order functions
Closures are anonymous functions
◦ Ruby, Groovy, Python; none of these
count!
Singly-linked immutable lists (cons
cells)
val names = quot;Chrisquot; :: quot;Joequot; :: Nil
val names2 = quot;Danielquot; :: names
17. Functional Trademarks
Higher-order functions
Closures are anonymous functions
◦ Ruby, Groovy, Python; none of these
count!
Singly-linked immutable lists (cons
cells)
Usually some form of type-inference
val me = quot;Danielquot;
// equivalent to...
val me: String = quot;Danielquot;
18. Functional Trademarks
Higher-order functions
Closures are anonymous functions
◦ Ruby, Groovy, Python; none of these
count!
Singly-linked immutable lists (cons
cells)
Usually some form of type-inference
foreach { s => println(s) }
19. Functional Trademarks
Higher-order functions
Closures are anonymous functions
◦ Ruby, Groovy, Python; none of these
count!
Singly-linked immutable lists (cons
cells)
Usually some form of type-inference
Immutable by default (or encouraged)
val me = quot;Danielquot;
var me = quot;Danielquot;
20. What does this buy you?
Modularity (separation of concerns)
Understandability
No more “spooky action at a distance”
…
21. What does this buy you?
public class Company {
private List<Person> employees;
public List<Person> getEmployees() {
return employees;
}
public void addEmployee(Person p) {
if (p.isAlive()) {
employees.add(p);
}
}
}
22. What does this buy you?
Modularity (separation of concerns)
Understandability
No more “spooky action at a distance”
Flexible libraries (more on this later)
Syntactic power (internal DSLs)
23. What does this buy you?
quot;vectorquot; should {
quot;store a single elementquot; in {
val prop = forAll { (i: Int, e: Int) =>
i >= 0 ==> { (vector(0) = e)(0) mustEqual e }
}
prop must pass
}
quot;implement lengthquot; in {
val prop = forAll { list: List[Int] =>
val vec = Vector(list:_*)
vec.length mustEqual list.length
}
prop must pass
}
}
24. Functional Idioms
Recursion instead of loops
◦ Scala helps with this by allowing methods
within methods
25. Functional Idioms
Recursion instead of loops
◦ Scala helps with this by allowing methods
within methods
def factorial(n: Int) = {
var back = 1
for (i <- 1 to n) {
back *= i
}
back
}
26. Functional Idioms
Recursion instead of loops
◦ Scala helps with this by allowing methods
within methods
def factorial(n: Int): Int = {
if (n == 1)
1
else
n * factorial(n - 1)
}
27. Functional Idioms
Recursion instead of loops
◦ Scala helps with this by allowing methods
within methods
def factorial(n: Int) = {
def loop(n: Int, acc: Int): Int = {
if (n == 1)
acc
else
loop(n - 1, acc * n)
}
loop(n, 1)
}
28. Functional Idioms
Recursion instead of loops
◦ Scala helps with this by allowing methods
within methods
Higher-order functions instead of
recursion
29. Functional Idioms
Recursion instead of loops
◦ Scala helps with this by allowing methods
within methods
Higher-order functions instead of
recursion
Combinators instead of higher-order
functions
30. Functional Idioms
Recursion instead of loops
◦ Scala helps with this by allowing methods
within methods
Higher-order functions instead of
recursion
Combinators instead of higher-order
functions
Monads!
31. Example #1
Retrieve structured, formatted data from
across multiple .properties files
and multiple keys within those files.
# daniel.properties # tim.properties
name.first = Daniel name.first = Timothy
name.last = Spiewak name.last = Olmsted
age = 21 age = 22
33. def toInt(s: String) = try {
s.toInt
} catch {
case _ => null
}
// uninteresting and ugly
def readFile(file: String): Map[String, String] = {
import collection.jcl.Hashtable
try {
val is = new BufferedInputStream(new FileInputStream(file))
val p = new Properties
p.load(is)
is.close()
new Hashtable(p).asInstanceOf[Hashtable[String, String]]
} catch {
case _ => null
}
}
34. import collection.mutable.ListBuffer
def readPeople(files: List[String]): List[Person] = {
val back = new ListBuffer[Person]
for (file <- files) {
val props = readFile(file)
if (props != null) {
if (props.contains(quot;name.firstquot;) &&
props.contains(quot;name.lastquot;) &&
props.contains(quot;agequot;)) {
val age = toInt(props(quot;agequot;))
if (age != null)
back += new Person(props(quot;name.firstquot;),
props(quot;name.lastquot;), age)
}
}
}
back.toList
}
36. def readPeople(files: List[String]): List[Person] = files match {
case file :: tail => {
val props = readFile(file)
val back = if (props != null) {
if (props.contains(quot;name.firstquot;) &&
props.contains(quot;name.lastquot;) &&
props.contains(quot;agequot;)) {
val age = toInt(props(quot;agequot;))
if (age != null)
new Person(props(quot;name.firstquot;), props(quot;name.lastquot;), age)
else
null
} else null
} else null
if (back != null)
back :: readPeople(tail)
else
readPeople(tail)
}
case Nil => Nil
}
38. def readPeople(files: List[String]): List[Person] = {
files.foldRight(List[String]()) { (file, people) =>
val props = readFile(file)
val back = if (props != null) {
if (props.contains(quot;name.firstquot;) &&
props.contains(quot;name.lastquot;) &&
props.contains(quot;agequot;)) {
val age = toInt(props(quot;agequot;))
if (age != null)
new Person(props(quot;name.firstquot;), props(quot;name.lastquot;), age)
else
null
} else null
} else null
if (back != null)
back :: people
else
people
}
}
43. def readPeople(files: List[String]) = {
import Function._
files flatMap readFile flatMap { props =>
val fNames = props get quot;name.firstquot;
val names = fNames flatMap {
(_, props get quot;name.lastquot;)
}
val data = names flatMap {
case (fn, ln) =>
(fn, ln, props get quot;agequot; map toInt)
}
data map tupled(new Person _)
}
}
44. What did we just see?
foldLeft / foldRight
◦ Catamorphisms
◦ Use when you want to reduce all of the
elements of a collection into a single
result
◦ Capable of almost anything!
45. What did we just see?
foldLeft / foldRight
def sum(nums: List[Int]) = {
nums.foldLeft(0) { (x, y) =>
x + y
}
}
46. What did we just see?
foldLeft / foldRight
def sum(nums: List[Int]) = {
nums.foldLeft(0) { _ + _ }
}
47. What did we just see?
foldLeft / foldRight
def sum(nums: List[Int]) = {
(0 /: nums) { _ + _ }
}
48. What did we just see?
foldLeft / foldRight
map
◦ Use when you want to transform every
element of a collection, leaving the results
in the corresponding location within a new
collection
49. What did we just see?
foldLeft / foldRight
map
val nums = List(quot;1quot;, quot;2quot;, quot;3quot;, quot;4quot;, quot;5quot;)
nums map { str => str.toInt }
50. What did we just see?
foldLeft / foldRight
map
val nums = List(quot;1quot;, quot;2quot;, quot;3quot;, quot;4quot;, quot;5quot;)
nums map { _.toInt }
51. What did we just see?
foldLeft / foldRight
map
flatMap (has two meanings)
◦ Collections: Use when you want to
transform every element optionally
◦ Monads: Should have really been called
“bind” (or >>=). More later…
52. What did we just see?
foldLeft / foldRight
map
flatMap (has two meanings)
def toCharArray(arr: Array[String]) = {
arr flatMap { _.toCharArray }
}
toCharArray(Array(quot;Danielquot;, quot;Spiewakquot;))
// =>
Array('D', 'a', 'n', 'i', 'e', 'l',
'S', 'p', 'i', 'e', 'w', 'a', 'k')
53. Other Common Util Methods
filter (used in for-comprehensions)
val nums = List(1, 2, 3, 4, 5)
nums filter { _ % 2 == 0 }
54. Other Common Util Methods
filter (used in for-comprehensions)
val nums = List(1, 2, 3, 4, 5)
nums filter (0 == 2 %)
55. Other Common Util Methods
filter (used in for-comprehensions)
zip / zipWithIndex
◦ zipWith (not available pre-2.8.0)
val evens = List(2, 4, 6)
val odds = List(1, 3, 5)
evens zip odds
// =>
List((1, 2), (3, 4), (5, 6))
56. Other Common Util Methods
filter (used in for-comprehensions)
zip / zipWithIndex
◦ zipWith (not available pre-2.8.0)
forall and exists
val nums = List(1, 2, 3, 4, 5)
nums forall { _ % 2 == 0 } // => false
57. Other Common Util Methods
filter (used in for-comprehensions)
zip / zipWithIndex
◦ zipWith (not available pre-2.8.0)
forall and exists
val nums = List(1, 2, 3, 4, 5)
nums exists { _ % 2 == 0 } // => true
58. Other Common Util Methods
filter (used in for-comprehensions)
zip / zipWithIndex
◦ zipWith (not available pre-2.8.0)
forall and exists
take and drop
val nums = List(5, 4, 3, 2, 1)
nums take 2
// =>
List(5, 4)
59. Other Common Util Methods
filter (used in for-comprehensions)
zip / zipWithIndex
◦ zipWith (not available pre-2.8.0)
forall and exists
take and drop
val nums = List(5, 4, 3, 2, 1)
nums drop 2
// =>
List(3, 2, 1)
60. Other Common Util Methods
filter (used in for-comprehensions)
zip / zipWithIndex
◦ zipWith (not available pre-2.8.0)
forall and exists
take and drop
foreach
val names = List(quot;Danielquot;, quot;Chrisquot;)
names foreach println
61. Example #2
Comparing the prefix of a List[Char]
to a given string.
List[Char] String Result
List('d', 'a', 'n', 'i', 'e', 'l') quot;danquot; true
List('d', 'a', 'n', 'i', 'e', 'l') quot;ielquot; false
List('t', 'e', 's', 't') quot;testingquot; false
List('t', 'e', 's', 't') quot;testquot; true
62. def isPrefix(chars: List[Char], str: String) = {
if (chars.lengthCompare(str.length) < 0) {
false
} else {
val trunc = chars take str.length
trunc.zipWithIndex forall {
case (c, i) => c == str(i)
}
}
}
63. More About Combinators
The “Essence of Functional
Programming”
Combine simple things to solve
complex problems
Very high level
Think about Lego™ bricks
64. More About Combinators
The best example: Parser
Combinators
def expr: Parser[Int] = (
num ~ quot;+quot; ~ expr ^^ { case (x, _, y) => x + y }
| num ~ quot;-quot; ~ expr ^^ { case (x, _, y) => x - y }
| num
)
def num = quot;quot;quot;d+quot;quot;quot;.r ^^ { _.toInt }
65. More About Combinators
The best example: Parser
Combinators
def expr: Parser[Int] = (
num ~ quot;+quot; ~ expr ^^ { case (x, _, y) => x + y }
| num ~ quot;-quot; ~ expr ^^ { case (x, _, y) => x - y }
| num
)
def num = quot;quot;quot;d+quot;quot;quot;.r ^^ { _.toInt }
expr(quot;12 + 7 - 4quot;) // => Success(15)
expr(quot;42quot;) // => Success(42)
67. More About Combinators
Three Types of Combinators
◦ Sequential (first a, then b)
◦ Disjunctive (either a, or b)
a | b
68. More About Combinators
Three Types of Combinators
◦ Sequential (first a, then b)
◦ Disjunctive (either a, or b)
◦ Literal (exactly foo)
quot;fooquot;
69. More About Combinators
Three Types of Combinators
◦ Sequential (first a, then b)
◦ Disjunctive (either a, or b)
◦ Literal (exactly foo)
Note: Our example uses a regular
expression parser, but that is only a
generalization of the literal parser
70. More About Combinators
Seldom created, often used
Good for problems which split into
smaller sub-problems
72. March of the Monads
Monads are not scary
Monad explanations are scary
73. March of the Monads
Monads are little containers for
encapsulating something
◦ What the “something” is depends on the
monad
An instance of a monad can be
“bound” together with another instance
of that monad
Most combinators are monads
74. March of the Monads
All monads have
◦ A type constructor
class Option[A] { … }
◦ A single-argument constructor
new Some(quot;one to watch over mequot;)
◦ A flatMap method which behaves
properly
a flatMap { v => v.next }
76. March of the Monads
Option
◦ This is what the Groovy folks really wanted
when they designed the “Elvis Operator”
Parser
◦ Sequential parser is really two bound
parsers
◦ Disjunctive parser uses an optional
monadic function: orElse
◦ Literal parser is the one-argument
constructor
Function1 (sort of)
◦ We could say that function composition is
77. Learn More
Read my blog! :-)
◦ http://www.codecommit.com/blog
Some better sources…
◦ http://apocalisp.wordpress.com/
◦ http://michid.wordpress.com/
◦ http://joelneely.wordpress.com/
◦ http://scala-blogs.org
A really good paper…
◦ Monadic Parser Combinators
(1996; Hutton, Meijer)