KEMBAR78
Building a Functional Stream in Scala | PDF
Building

A

F

am
tre
lS
ona
cti
un

in Scala
Derek Wyatt
Twitter: @derekwyatt
Email: derek@derekwyatt.org
Structure
ctional Data
A Fun
Structure
ctional Data
A Fun
Streams are infinite and lazy
Structure
ctional Data
A Fun
Streams are infinite and lazy
They can be built using functions
Structure
ctional Data
A Fun
Streams are infinite and lazy
They can be built using functions
Designing one is fun and instructive
Structure
ctional Data
A Fun
Streams are infinite and lazy
They can be built using functions
Designing one is fun and instructive
❊

I am not an FP expert (but I do play one in presentations).
Much of what you see has been learned from Functional Programming in Scala
by Paul Chiusano and Rúnar Bjarnason
Strictness

L ziness
a
vs.
Strictness

L ziness
a
vs.

def strict(row: DBRow): Unit = {	
if (somecondition)	
// do something with row	
}
Strictness
fetchRow(42)

L ziness
a
vs.

def strict(row: DBRow): Unit = {	
if (somecondition)	
// do something with row	
}
Strictness
fetchRow(42)

L ziness
a
vs.

def strict(row: DBRow): Unit = {	
if (somecondition)	
// do something with row	
}
def lazy(row: => DBRow): Unit = {	
if (somecondition)	
// do something with row	
}
Strictness
fetchRow(42)

L ziness
a
vs.

se
fal
def strict(row: DBRow): Unit = {	
=
ion
if (somecondition)	
t
ndi
co
// do something with row	
me
so
}
def lazy(row: => DBRow): Unit = {	
if (somecondition)	
// do something with row	
}
Strictness
fetchRow(42)

evaluated

L ziness
a
vs.

se
Not Evaluated
fal
def strict(row: DBRow): Unit = {	
=
ion
if (somecondition)	
t
ndi
co
// do something with row	
me
so
}
def lazy(row: => DBRow): Unit = {	
if (somecondition)	
// do something with row	
}
List: A “Strict” Data Type
List: A “Strict” Data Type
(1 to 10).toList map { i =>
println(s”list -> map $i”)
i + 10
} filter { i =>
println(s”list -> filter $i”)
i % 2 == 0
}

[1, 2, ... 10]

toList
List: A “Strict” Data Type
(1 to 10).toList map { i =>
println(s”list -> map $i”)
i + 10
} filter { i =>
println(s”list -> filter $i”)
i % 2 == 0
}

[1, 2, ... 10]

Map [11, 12, ... 20]

toList

//
//
//
//
//

list
list
...	
list
list

-> map 1	
-> map 2	
-> map 9	
-> map 10
List: A “Strict” Data Type
(1 to 10).toList map { i =>
println(s”list -> map $i”)
i + 10
} filter { i =>
println(s”list -> filter $i”)
i % 2 == 0
}

[1, 2, ... 10]

Map [11, 12, ... 20]
Filter

toList

[12, 14, ... 20]

//
//
//
//
//
//
//
//
//
//

list
list
...	
list
list
list
list
...	
list
list

-> map 1	
-> map 2	
->
->
->
->

map 9	
map 10
10	
filter 11	
filter 12	

-> filter 19	
-> filter 20
ams: Lazy Ass Seqs
Stre
ams: Lazy Ass Seqs
Stre
Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}
ams: Lazy Ass Seqs
Stre
Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}

[1, ?]

Stream(1 to 10)
ams: Lazy Ass Seqs
Stre
Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}

[1, ?]
[11, ?]

Stream(1 to 10)
Map
ams: Lazy Ass Seqs
Stre
Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}

[1, ?]
[11, ?]

Stream(1 to 10)
Map

[12, ?]

Filter

The ?‘s are, essentially
functions
ams: Lazy Ass Seqs
Stre
Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}

[1, ?]
[11, ?]

Stream(1 to 10)
Map

[12, ?]

Filter

The ?‘s are, essentially
functions
ative purposes only
For il ustr
ams: Lazy Ass Seqs
Stre
Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}

[1, ?]
[11, ?]

Stream(1 to 10)
Map

[12, ?]

Filter

The ?‘s are, essentially
functions
ative purposes only
For il ustr

Stream transformation does not require traversal
ams: Lazy Ass Seqs
Stre
Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}

[1, ?]
[11, ?]

Stream(1 to 10)
Map

[12, ?]

Filter

The ?‘s are, essentially
functions
ative purposes only
For il ustr

Stream transformation does not require traversal
Transformations are applied on-demand as
traversal happens
Stream Definition
Stream Definition
trait Stream[+A] {
def uncons: Option[(A, Stream[A])]
}
Stream Definition
trait Stream[+A] {
def uncons: Option[(A, Stream[A])]
}

The “data” structure
Stream Definition
trait Stream[+A] {
def uncons: Option[(A, Stream[A])]
}

The “data” structure
Constructors

object Stream {
def empty[A]: Stream[A] = new Stream[A] {
def uncons = None
}
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = new Stream[A] {
def uncons = Some((hd, tl))
}
}
Stream Definition
trait Stream[+A] {
def uncons: Option[(A, Stream[A])]
}

The “data” structure
Constructors

object Stream {
def empty[A]: Stream[A] = new Stream[A] {
def uncons = None
}
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = new Stream[A] {
def uncons = Some((hd, tl))
}
}
Constructing Streams
Constructing Streams
val streamOfOne = cons(1, empty[Int])
val streamOfThree = cons(1, cons(2, cons(3, empty[Int])))
Constructing Streams
val streamOfOne = cons(1, empty[Int])
val streamOfThree = cons(1, cons(2, cons(3, empty[Int])))

Well, that

SUCKS...
Constructing Streams
val streamOfOne = cons(1, empty[Int])
val streamOfThree = cons(1, cons(2, cons(3, empty[Int])))

Well, that

SUCKS...

object Stream {
def apply[A](as: A*): Stream[A] = {

if (as.isEmpty) empty[A]
else cons(as.head, apply(as.tail:_*))
}
}
Constructing Streams
val streamOfOne = cons(1, empty[Int])
val streamOfThree = cons(1, cons(2, cons(3, empty[Int])))

Well, that

SUCKS...

val three = Stream(1, 2, 3)

object Stream {
def apply[A](as: A*): Stream[A] = {

if (as.isEmpty) empty[A]
else cons(as.head, apply(as.tail:_*))
}
}
The Stream... so far
The Stream... so far
trait Stream[+A] {
def uncons: Option[(A, Stream[A])]
}
!

object Stream {
def empty[A]: Stream[A] = new Stream[A] {
def uncons = None
}
def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = new Stream[A] {
def uncons = Some((hd, tl))
}
def apply[A](as: A*): Stream[A] = {

if (as.isEmpty) empty[A]
else cons(as.head, apply(as.tail:_*))
}
}
Right (does it all)
fold
(1 to 4).foldRight(z)(f)
Right (does it all)
fold
(1 to 4).foldRight(z)(f)
makes

f(1, f(2, f(3, f(4, z))))
Right (does it all)
fold
(1 to 4).foldRight(z)(f)
makes

or

f(1, f(2, f(3, f(4, z))))
f(1,
f(1,
f(1,
f(1,
f(1,
f(1,
f(1,
res4

...
f(2, ...
f(2, f(3, ...
f(2, f(3, f(4, z))))
f(2, f(3, res1)))
f(2, res2))
res3)
Right (does it all)
fold
(1 to 4).foldRight(z)(f)
Builds a functional
structure “to the right”,
pushing successive
evaluation to the
second parameter.

makes

or

Each function’s second
parameter must be evaluated
before its predecessor can be
evaluated.

f(1, f(2, f(3, f(4, z))))
f(1,
f(1,
f(1,
f(1,
f(1,
f(1,
f(1,
res4

...
f(2, ...
f(2, f(3, ...
f(2, f(3, f(4, z))))
f(2, f(3, res1)))
f(2, res2))
res3)
foldRight for Lists
foldRight for Lists
case class List[+A](head: A, tail: List[A]) {
def foldRight[B](z: B)(f: (A, B) => B): B =
if (this.isEmpty) z
else f(head, tail.foldRight(z)(f))
}
foldRight for Lists
case class List[+A](head: A, tail: List[A]) {
def foldRight[B](z: B)(f: (A, B) => B): B =
if (this.isEmpty) z
else f(head, tail.foldRight(z)(f))
}
val sum
// f(1,
// f(1,
// f(1,
// f(1,
// f(1,
// f(1,
// f(1,
// 10

= (1 to 4).foldRight(0)(_ + _)
tail.foldRight...
f(2, tail.foldRight...
f(2, f(3, tail.foldRight...
f(2, f(3, f(4, 0))))
f(2, f(3, 4)))
f(2, 7))
9)
foldRight for Lists
case class List[+A](head: A, tail: List[A]) {
def foldRight[B](z: B)(f: (A, B) => B): B =
if (this.isEmpty) z
else f(head, tail.foldRight(z)(f))
}

Here A and B are both
Ints but they need not
be. Note that full
recursive expansion takes
place at the call site.

val sum
// f(1,
// f(1,
// f(1,
// f(1,
// f(1,
// f(1,
// f(1,
// 10

= (1 to 4).foldRight(0)(_ + _)
tail.foldRight...
f(2, tail.foldRight...
f(2, f(3, tail.foldRight...
f(2, f(3, f(4, 0))))
f(2, f(3, 4)))
f(2, 7))
9)
Strictly Folding Right
Strictly Folding Right
No application of ‘f’ can complete until all of its parameters
℥
have been applied
Strictly Folding Right
No application of ‘f’ can complete until all of its parameters
℥
have been applied
Strictly speaking, this means that foldRight must fully
℥
expand into a deeply nested function application
Strictly Folding Right
No application of ‘f’ can complete until all of its parameters
℥
have been applied
Strictly speaking, this means that foldRight must fully
℥
expand into a deeply nested function application
If the collection is infinite, or even significantly large, your
℥
application is doomed
Strictly Folding Right
No application of ‘f’ can complete until all of its parameters
℥
have been applied
Strictly speaking, this means that foldRight must fully
℥
expand into a deeply nested function application
If the collection is infinite, or even significantly large, your
℥
application is doomed
Building foldRight
Building foldRight
trait Stream[+A] {
def foldRight[B](z: ? B)(f: (A, ? B) => B): B
}
Building foldRight
trait Stream[+A]Stream[+A] {
trait {
def uncons: Option[(A, Stream[A])]
def foldRight[B](z: ? B)(f: (A, ? B) => B): B
!
}
def foldRight[B](z: => B)(f: (A, => B) => B): B = uncons match {
case None => z
case Some((hd, tl)) => f(hd, tl.foldRight(z)(f))
}
}
Building foldRight
trait Stream[+A]Stream[+A] {
trait {
def uncons: Option[(A, Stream[A])]
def foldRight[B](z: ? B)(f: (A, ? B) => B): B
!
}
def foldRight[B](z: => B)(f: (A, => B) => B): B = uncons match {
case None => z
case Some((hd, tl)) => f(hd, tl.foldRight(z)(f))
}
}

A profound change!
Building foldRight
trait Stream[+A]Stream[+A] {
trait {
def uncons: Option[(A, Stream[A])]
def foldRight[B](z: ? B)(f: (A, ? B) => B): B
!
}
def foldRight[B](z: => B)(f: (A, => B) => B): B = uncons match {
case None => z
case Some((hd, tl)) => f(hd, tl.foldRight(z)(f))
}
}

The recursive call is no longer evaluated at the call site
because ‘f’ receives it by name
Learning to Relax
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B

At this point, you’re potentially confused
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B

At this point, you’re potentially confused
What good is it to have a lazy => B when foldRight must return a strict B?
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B

At this point, you’re potentially confused
What good is it to have a lazy => B when foldRight must return a strict B?
def sum(ints: Stream[Int]): Int = ints.foldRight(0)(_ + _)
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B

At this point, you’re potentially confused
What good is it to have a lazy => B when foldRight must return a strict B?
def sum(ints: Stream[Int]): Int = ints.foldRight(0)(_ + _)

val neverGetsHere = sum(streamOfNaturalNumbers)
Learning to Relax

!
e
yp

def foldRight[B](z: => B)(f: (A, => B) => B): B

T
”
T

IC
TR
S

At this point, you’re potentially confused

“
a
s

What good is it to have a lazy => B when foldRight must return a strict B?

I

i
t
n

def sum(ints: Stream[Int]): Int = ints.foldRight(0)(_ + _)

val neverGetsHere = sum(streamOfNaturalNumbers)
Learning to Relax
Learning to Relax
Ok, I kinda lied… it’s not that Int is a “strict type”
(You can, of course, do something useful with an Int… such as)
Learning to Relax
Ok, I kinda lied… it’s not that Int is a “strict type”
(You can, of course, do something useful with an Int… such as)
def sumToN(ints: Stream[Int], to: Int): Int =
ints.take(to).foldRight(0)(_ + _)
Learning to Relax
Ok, I kinda lied… it’s not that Int is a “strict type”
(You can, of course, do something useful with an Int… such as)
def sumToN(ints: Stream[Int], to: Int): Int =
ints.take(to).foldRight(0)(_ + _)

It’s just that the strict type can cause a bit of confusion because of its
non-lazy nature.
Learning to Relax
Ok, I kinda lied… it’s not that Int is a “strict type”
(You can, of course, do something useful with an Int… such as)
def sumToN(ints: Stream[Int], to: Int): Int =
ints.take(to).foldRight(0)(_ + _)

It’s just that the strict type can cause a bit of confusion because of its
non-lazy nature.
The more “interesting” stuff, though happens when you can continue
beings lazy and stay within the realm of the infinite
Learning to Relax
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B

Switching to a lazy (by name) parameter gives ‘f’ control
over the recursion
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B

Switching to a lazy (by name) parameter gives ‘f’ control
over the recursion
‘f’ can decide to delay the execution of its second parameter,
eliminating the recursive call, returning control to the caller
Learning to Relax
def foldRight[B](z: => B)(f: (A, => B) => B): B

Switching to a lazy (by name) parameter gives ‘f’ control
over the recursion
‘f’ can decide to delay the execution of its second parameter,
eliminating the recursive call, returning control to the caller
However, you can’t just delay a computation without shoving
it into some sort of context...
Learning to Relax

M
A
E
R
T
t!
S
x
e
e
t
h
n
T
o
c
t
a
th
is
def foldRight[B](z: => B)(f: (A, => B) => B): B

Switching to a lazy (by name) parameter gives ‘f’ control
over the recursion
‘f’ can decide to delay the execution of its second parameter,
eliminating the recursive call, returning control to the caller
However, you can’t just delay a computation without shoving
it into some sort of context...
A Match Made in Laziness

=>

+

Non-Strict
Result

=

Lazy
Stream
Enter... Map
Enter... Map
def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>
cons(f(head), tail)
}
Enter... Map
def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>
cons(f(head), tail)
}

def foldRight[B](z: => B)(f: (A, => B) => B): B
Enter... Map
St
r

ea

def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>
cons(f(head), tail)
}

Stream!

St
r

ea

m

St
r

!

ea

m

m

!

!

def foldRight[B](z: => B)(f: (A, => B) => B): B
Enter... Filter
Enter... Filter
def filter(p: A => Boolean): Stream[A] =
foldRight(empty[A]) { (head, tail) =>
if (p(head)) cons(head, tail)
else tail
}
Enter... Filter
def filter(p: A => Boolean): Stream[A] =
foldRight(empty[A]) { (head, tail) =>
if (p(head)) cons(head, tail)
else tail
}

def foldRight[B](z: => B)(f: (A, => B) => B): B
Enter... Filter
def filter(p: A => Boolean): Stream[A] =
foldRight(empty[A]) { (head, tail) =>
if (p(head)) cons(head, tail)
else tail
St
re
}

am

Stream!

!

St
r

ea

St
r

ea

m

m

!

def foldRight[B](z: => B)(f: (A, => B) => B): B

!
Returning Streams
Returning Streams
Both map and filter return Streams
Returning Streams
Both map and filter return Streams
Stream’s constructors eval neither head nor tail
Returning Streams
Both map and filter return Streams
Stream’s constructors eval neither head nor tail
This allows for the chaining of laziness from the
by name parameter of foldRight, into the return value
Returning Streams
Returning Streams
def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>
Returning Streams
def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>

Not Eval’d
Returning Streams
def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>
!
!
!

Not Eval’d

!

Not Eval’d

!

cons(f(head), tail)
}
Returning Streams
def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>
!
!
!

Not Eval’d

Not Eval’d

!

Not Eval’d

!

cons(f(head), tail)
}
Returning Streams
def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>
!
!
!

Not Eval’d

Not Eval’d

!

Not Eval’d

!

cons(f(head), tail)
}

Jeez, does
this code
even do
anything!?
Let’s find Out...
Let’s find Out...
Stream(1, 2, 3, 4, 5) map { i =>
println(s"map -> $i")
i * i
}
Let’s find Out...
Stream(1, 2, 3, 4, 5) map { i =>
println(s"map -> $i")
i * i
}

Which prints...

def map[T](f: A => T): Stream[T] =
foldRight(empty[T]) { (head, tail) =>
cons(f(head), tail)
}

Remember
that...
and...

def foldRight[B](z: => B)(f: (A, => B) => B): B
def cons[A](hd: => A, tl: => Stream[A]): Stream[A]

and...
This page intentionally left blank
Putting it All Together
Putting it All Together
val s = Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}
Putting it All Together
val s = Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}

Prints nothing.
OK
Putting it All Together
val s = Stream(1 to 10) map { i =>
println(s”stream -> map $i”)
i + 10
} filter { i =>
println(s”stream -> filter $i”)
i % 2 == 0
}

def toList: List[A] = uncons match {
case None => Nil
case Some((h, t)) => h :: t.toList
}

Prints nothing.
OK

add toList()
Evaluating
Evaluating
val numList = s.toList
Evaluating
val numList = s.toList

//
//
//
//
//
//
//

stream
stream
stream
stream
...	
stream
stream

->
->
->
->

map 1	
filter 11	
map 2	
filter 12	

-> map 10	
-> filter 20
Evaluating
val numList = s.toList

def take(n: Int): Stream[A] =
uncons match {
case Some((h, t)) if n > 0 =>
cons(h, t.take(n - 1))
case _ =>
empty
}

//
//
//
//
//
//
//

stream
stream
stream
stream
...	
stream
stream

->
->
->
->

map 1	
filter 11	
map 2	
filter 12	

-> map 10	
-> filter 20
Evaluating
val numList = s.toList

def take(n: Int): Stream[A] =
uncons match {
case Some((h, t)) if n > 0 =>
cons(h, t.take(n - 1))
case _ =>
empty
}

val numList = s.take(1).toList

//
//
//
//
//
//
//

stream
stream
stream
stream
...	
stream
stream

->
->
->
->

map 1	
filter 11	
map 2	
filter 12	

-> map 10	
-> filter 20
Evaluating
val numList = s.toList

def take(n: Int): Stream[A] =
uncons match {
case Some((h, t)) if n > 0 =>
cons(h, t.take(n - 1))
case _ =>
empty
}

val numList = s.take(1).toList

//
//
//
//
//
//
//

stream
stream
stream
stream
...	
stream
stream

//
//
//
//

->
->
->
->

map 1	
filter 11	
map 2	
filter 12	

-> map 10	
-> filter 20

stream
stream
stream
stream

->
->
->
->

map 1	
filter 11	
map 2	
filter 12
It’s All About Functions
It’s All About Functions
Functions hold the values
It’s All About Functions
Functions hold the values
Higher order functions pile functions on functions
It’s All About Functions
Functions hold the values
Higher order functions pile functions on functions
Even functions like take() merely store functions
It’s All About Functions
Functions hold the values
Higher order functions pile functions on functions
Even functions like take() merely store functions
Nothing “real” happens until you need it to happen
It’s All About Functions
Functions hold the values
Higher order functions pile functions on functions
Even functions like take() merely store functions
Nothing “real” happens until you need it to happen

!
y
in
h
S
ms
rea
St
ss
yA
az

L

i

Brought to you
Sincere Couch Potato
em
s

Derek Wyatt
Twitter: @derekwyatt
Email: derek@derekwyatt.org

Building a Functional Stream in Scala

  • 1.
  • 2.
  • 3.
  • 4.
    Structure ctional Data A Fun Streamsare infinite and lazy They can be built using functions
  • 5.
    Structure ctional Data A Fun Streamsare infinite and lazy They can be built using functions Designing one is fun and instructive
  • 6.
    Structure ctional Data A Fun Streamsare infinite and lazy They can be built using functions Designing one is fun and instructive ❊ I am not an FP expert (but I do play one in presentations). Much of what you see has been learned from Functional Programming in Scala by Paul Chiusano and Rúnar Bjarnason
  • 7.
  • 8.
    Strictness L ziness a vs. def strict(row:DBRow): Unit = { if (somecondition) // do something with row }
  • 9.
    Strictness fetchRow(42) L ziness a vs. def strict(row:DBRow): Unit = { if (somecondition) // do something with row }
  • 10.
    Strictness fetchRow(42) L ziness a vs. def strict(row:DBRow): Unit = { if (somecondition) // do something with row } def lazy(row: => DBRow): Unit = { if (somecondition) // do something with row }
  • 11.
    Strictness fetchRow(42) L ziness a vs. se fal def strict(row:DBRow): Unit = { = ion if (somecondition) t ndi co // do something with row me so } def lazy(row: => DBRow): Unit = { if (somecondition) // do something with row }
  • 12.
    Strictness fetchRow(42) evaluated L ziness a vs. se Not Evaluated fal defstrict(row: DBRow): Unit = { = ion if (somecondition) t ndi co // do something with row me so } def lazy(row: => DBRow): Unit = { if (somecondition) // do something with row }
  • 13.
  • 14.
    List: A “Strict”Data Type (1 to 10).toList map { i => println(s”list -> map $i”) i + 10 } filter { i => println(s”list -> filter $i”) i % 2 == 0 } [1, 2, ... 10] toList
  • 15.
    List: A “Strict”Data Type (1 to 10).toList map { i => println(s”list -> map $i”) i + 10 } filter { i => println(s”list -> filter $i”) i % 2 == 0 } [1, 2, ... 10] Map [11, 12, ... 20] toList // // // // // list list ... list list -> map 1 -> map 2 -> map 9 -> map 10
  • 16.
    List: A “Strict”Data Type (1 to 10).toList map { i => println(s”list -> map $i”) i + 10 } filter { i => println(s”list -> filter $i”) i % 2 == 0 } [1, 2, ... 10] Map [11, 12, ... 20] Filter toList [12, 14, ... 20] // // // // // // // // // // list list ... list list list list ... list list -> map 1 -> map 2 -> -> -> -> map 9 map 10 10 filter 11 filter 12 -> filter 19 -> filter 20
  • 17.
    ams: Lazy AssSeqs Stre
  • 18.
    ams: Lazy AssSeqs Stre Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 }
  • 19.
    ams: Lazy AssSeqs Stre Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 } [1, ?] Stream(1 to 10)
  • 20.
    ams: Lazy AssSeqs Stre Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 } [1, ?] [11, ?] Stream(1 to 10) Map
  • 21.
    ams: Lazy AssSeqs Stre Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 } [1, ?] [11, ?] Stream(1 to 10) Map [12, ?] Filter The ?‘s are, essentially functions
  • 22.
    ams: Lazy AssSeqs Stre Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 } [1, ?] [11, ?] Stream(1 to 10) Map [12, ?] Filter The ?‘s are, essentially functions ative purposes only For il ustr
  • 23.
    ams: Lazy AssSeqs Stre Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 } [1, ?] [11, ?] Stream(1 to 10) Map [12, ?] Filter The ?‘s are, essentially functions ative purposes only For il ustr Stream transformation does not require traversal
  • 24.
    ams: Lazy AssSeqs Stre Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 } [1, ?] [11, ?] Stream(1 to 10) Map [12, ?] Filter The ?‘s are, essentially functions ative purposes only For il ustr Stream transformation does not require traversal Transformations are applied on-demand as traversal happens
  • 25.
  • 26.
    Stream Definition trait Stream[+A]{ def uncons: Option[(A, Stream[A])] }
  • 27.
    Stream Definition trait Stream[+A]{ def uncons: Option[(A, Stream[A])] } The “data” structure
  • 28.
    Stream Definition trait Stream[+A]{ def uncons: Option[(A, Stream[A])] } The “data” structure Constructors object Stream { def empty[A]: Stream[A] = new Stream[A] { def uncons = None } def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = new Stream[A] { def uncons = Some((hd, tl)) } }
  • 29.
    Stream Definition trait Stream[+A]{ def uncons: Option[(A, Stream[A])] } The “data” structure Constructors object Stream { def empty[A]: Stream[A] = new Stream[A] { def uncons = None } def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = new Stream[A] { def uncons = Some((hd, tl)) } }
  • 30.
  • 31.
    Constructing Streams val streamOfOne= cons(1, empty[Int]) val streamOfThree = cons(1, cons(2, cons(3, empty[Int])))
  • 32.
    Constructing Streams val streamOfOne= cons(1, empty[Int]) val streamOfThree = cons(1, cons(2, cons(3, empty[Int]))) Well, that SUCKS...
  • 33.
    Constructing Streams val streamOfOne= cons(1, empty[Int]) val streamOfThree = cons(1, cons(2, cons(3, empty[Int]))) Well, that SUCKS... object Stream { def apply[A](as: A*): Stream[A] = {
 if (as.isEmpty) empty[A] else cons(as.head, apply(as.tail:_*)) } }
  • 34.
    Constructing Streams val streamOfOne= cons(1, empty[Int]) val streamOfThree = cons(1, cons(2, cons(3, empty[Int]))) Well, that SUCKS... val three = Stream(1, 2, 3) object Stream { def apply[A](as: A*): Stream[A] = {
 if (as.isEmpty) empty[A] else cons(as.head, apply(as.tail:_*)) } }
  • 35.
  • 36.
    The Stream... sofar trait Stream[+A] { def uncons: Option[(A, Stream[A])] } ! object Stream { def empty[A]: Stream[A] = new Stream[A] { def uncons = None } def cons[A](hd: => A, tl: => Stream[A]): Stream[A] = new Stream[A] { def uncons = Some((hd, tl)) } def apply[A](as: A*): Stream[A] = {
 if (as.isEmpty) empty[A] else cons(as.head, apply(as.tail:_*)) } }
  • 37.
    Right (does itall) fold (1 to 4).foldRight(z)(f)
  • 38.
    Right (does itall) fold (1 to 4).foldRight(z)(f) makes f(1, f(2, f(3, f(4, z))))
  • 39.
    Right (does itall) fold (1 to 4).foldRight(z)(f) makes or f(1, f(2, f(3, f(4, z)))) f(1, f(1, f(1, f(1, f(1, f(1, f(1, res4 ... f(2, ... f(2, f(3, ... f(2, f(3, f(4, z)))) f(2, f(3, res1))) f(2, res2)) res3)
  • 40.
    Right (does itall) fold (1 to 4).foldRight(z)(f) Builds a functional structure “to the right”, pushing successive evaluation to the second parameter. makes or Each function’s second parameter must be evaluated before its predecessor can be evaluated. f(1, f(2, f(3, f(4, z)))) f(1, f(1, f(1, f(1, f(1, f(1, f(1, res4 ... f(2, ... f(2, f(3, ... f(2, f(3, f(4, z)))) f(2, f(3, res1))) f(2, res2)) res3)
  • 41.
  • 42.
    foldRight for Lists caseclass List[+A](head: A, tail: List[A]) { def foldRight[B](z: B)(f: (A, B) => B): B = if (this.isEmpty) z else f(head, tail.foldRight(z)(f)) }
  • 43.
    foldRight for Lists caseclass List[+A](head: A, tail: List[A]) { def foldRight[B](z: B)(f: (A, B) => B): B = if (this.isEmpty) z else f(head, tail.foldRight(z)(f)) } val sum // f(1, // f(1, // f(1, // f(1, // f(1, // f(1, // f(1, // 10 = (1 to 4).foldRight(0)(_ + _) tail.foldRight... f(2, tail.foldRight... f(2, f(3, tail.foldRight... f(2, f(3, f(4, 0)))) f(2, f(3, 4))) f(2, 7)) 9)
  • 44.
    foldRight for Lists caseclass List[+A](head: A, tail: List[A]) { def foldRight[B](z: B)(f: (A, B) => B): B = if (this.isEmpty) z else f(head, tail.foldRight(z)(f)) } Here A and B are both Ints but they need not be. Note that full recursive expansion takes place at the call site. val sum // f(1, // f(1, // f(1, // f(1, // f(1, // f(1, // f(1, // 10 = (1 to 4).foldRight(0)(_ + _) tail.foldRight... f(2, tail.foldRight... f(2, f(3, tail.foldRight... f(2, f(3, f(4, 0)))) f(2, f(3, 4))) f(2, 7)) 9)
  • 45.
  • 46.
    Strictly Folding Right Noapplication of ‘f’ can complete until all of its parameters ℥ have been applied
  • 47.
    Strictly Folding Right Noapplication of ‘f’ can complete until all of its parameters ℥ have been applied Strictly speaking, this means that foldRight must fully ℥ expand into a deeply nested function application
  • 48.
    Strictly Folding Right Noapplication of ‘f’ can complete until all of its parameters ℥ have been applied Strictly speaking, this means that foldRight must fully ℥ expand into a deeply nested function application If the collection is infinite, or even significantly large, your ℥ application is doomed
  • 49.
    Strictly Folding Right Noapplication of ‘f’ can complete until all of its parameters ℥ have been applied Strictly speaking, this means that foldRight must fully ℥ expand into a deeply nested function application If the collection is infinite, or even significantly large, your ℥ application is doomed
  • 50.
  • 51.
    Building foldRight trait Stream[+A]{ def foldRight[B](z: ? B)(f: (A, ? B) => B): B }
  • 52.
    Building foldRight trait Stream[+A]Stream[+A]{ trait { def uncons: Option[(A, Stream[A])] def foldRight[B](z: ? B)(f: (A, ? B) => B): B ! } def foldRight[B](z: => B)(f: (A, => B) => B): B = uncons match { case None => z case Some((hd, tl)) => f(hd, tl.foldRight(z)(f)) } }
  • 53.
    Building foldRight trait Stream[+A]Stream[+A]{ trait { def uncons: Option[(A, Stream[A])] def foldRight[B](z: ? B)(f: (A, ? B) => B): B ! } def foldRight[B](z: => B)(f: (A, => B) => B): B = uncons match { case None => z case Some((hd, tl)) => f(hd, tl.foldRight(z)(f)) } } A profound change!
  • 54.
    Building foldRight trait Stream[+A]Stream[+A]{ trait { def uncons: Option[(A, Stream[A])] def foldRight[B](z: ? B)(f: (A, ? B) => B): B ! } def foldRight[B](z: => B)(f: (A, => B) => B): B = uncons match { case None => z case Some((hd, tl)) => f(hd, tl.foldRight(z)(f)) } } The recursive call is no longer evaluated at the call site because ‘f’ receives it by name
  • 55.
  • 56.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B
  • 57.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B At this point, you’re potentially confused
  • 58.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B At this point, you’re potentially confused What good is it to have a lazy => B when foldRight must return a strict B?
  • 59.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B At this point, you’re potentially confused What good is it to have a lazy => B when foldRight must return a strict B? def sum(ints: Stream[Int]): Int = ints.foldRight(0)(_ + _)
  • 60.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B At this point, you’re potentially confused What good is it to have a lazy => B when foldRight must return a strict B? def sum(ints: Stream[Int]): Int = ints.foldRight(0)(_ + _) val neverGetsHere = sum(streamOfNaturalNumbers)
  • 61.
    Learning to Relax ! e yp deffoldRight[B](z: => B)(f: (A, => B) => B): B T ” T IC TR S At this point, you’re potentially confused “ a s What good is it to have a lazy => B when foldRight must return a strict B? I i t n def sum(ints: Stream[Int]): Int = ints.foldRight(0)(_ + _) val neverGetsHere = sum(streamOfNaturalNumbers)
  • 62.
  • 63.
    Learning to Relax Ok,I kinda lied… it’s not that Int is a “strict type” (You can, of course, do something useful with an Int… such as)
  • 64.
    Learning to Relax Ok,I kinda lied… it’s not that Int is a “strict type” (You can, of course, do something useful with an Int… such as) def sumToN(ints: Stream[Int], to: Int): Int = ints.take(to).foldRight(0)(_ + _)
  • 65.
    Learning to Relax Ok,I kinda lied… it’s not that Int is a “strict type” (You can, of course, do something useful with an Int… such as) def sumToN(ints: Stream[Int], to: Int): Int = ints.take(to).foldRight(0)(_ + _) It’s just that the strict type can cause a bit of confusion because of its non-lazy nature.
  • 66.
    Learning to Relax Ok,I kinda lied… it’s not that Int is a “strict type” (You can, of course, do something useful with an Int… such as) def sumToN(ints: Stream[Int], to: Int): Int = ints.take(to).foldRight(0)(_ + _) It’s just that the strict type can cause a bit of confusion because of its non-lazy nature. The more “interesting” stuff, though happens when you can continue beings lazy and stay within the realm of the infinite
  • 67.
  • 68.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B
  • 69.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B Switching to a lazy (by name) parameter gives ‘f’ control over the recursion
  • 70.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B Switching to a lazy (by name) parameter gives ‘f’ control over the recursion ‘f’ can decide to delay the execution of its second parameter, eliminating the recursive call, returning control to the caller
  • 71.
    Learning to Relax deffoldRight[B](z: => B)(f: (A, => B) => B): B Switching to a lazy (by name) parameter gives ‘f’ control over the recursion ‘f’ can decide to delay the execution of its second parameter, eliminating the recursive call, returning control to the caller However, you can’t just delay a computation without shoving it into some sort of context...
  • 72.
    Learning to Relax M A E R T t! S x e e t h n T o c t a th is deffoldRight[B](z: => B)(f: (A, => B) => B): B Switching to a lazy (by name) parameter gives ‘f’ control over the recursion ‘f’ can decide to delay the execution of its second parameter, eliminating the recursive call, returning control to the caller However, you can’t just delay a computation without shoving it into some sort of context...
  • 73.
    A Match Madein Laziness => + Non-Strict Result = Lazy Stream
  • 74.
  • 75.
    Enter... Map def map[T](f:A => T): Stream[T] = foldRight(empty[T]) { (head, tail) => cons(f(head), tail) }
  • 76.
    Enter... Map def map[T](f:A => T): Stream[T] = foldRight(empty[T]) { (head, tail) => cons(f(head), tail) } def foldRight[B](z: => B)(f: (A, => B) => B): B
  • 77.
    Enter... Map St r ea def map[T](f:A => T): Stream[T] = foldRight(empty[T]) { (head, tail) => cons(f(head), tail) } Stream! St r ea m St r ! ea m m ! ! def foldRight[B](z: => B)(f: (A, => B) => B): B
  • 78.
  • 79.
    Enter... Filter def filter(p:A => Boolean): Stream[A] = foldRight(empty[A]) { (head, tail) => if (p(head)) cons(head, tail) else tail }
  • 80.
    Enter... Filter def filter(p:A => Boolean): Stream[A] = foldRight(empty[A]) { (head, tail) => if (p(head)) cons(head, tail) else tail } def foldRight[B](z: => B)(f: (A, => B) => B): B
  • 81.
    Enter... Filter def filter(p:A => Boolean): Stream[A] = foldRight(empty[A]) { (head, tail) => if (p(head)) cons(head, tail) else tail St re } am Stream! ! St r ea St r ea m m ! def foldRight[B](z: => B)(f: (A, => B) => B): B !
  • 82.
  • 83.
    Returning Streams Both mapand filter return Streams
  • 84.
    Returning Streams Both mapand filter return Streams Stream’s constructors eval neither head nor tail
  • 85.
    Returning Streams Both mapand filter return Streams Stream’s constructors eval neither head nor tail This allows for the chaining of laziness from the by name parameter of foldRight, into the return value
  • 86.
  • 87.
    Returning Streams def map[T](f:A => T): Stream[T] = foldRight(empty[T]) { (head, tail) =>
  • 88.
    Returning Streams def map[T](f:A => T): Stream[T] = foldRight(empty[T]) { (head, tail) => Not Eval’d
  • 89.
    Returning Streams def map[T](f:A => T): Stream[T] = foldRight(empty[T]) { (head, tail) => ! ! ! Not Eval’d ! Not Eval’d ! cons(f(head), tail) }
  • 90.
    Returning Streams def map[T](f:A => T): Stream[T] = foldRight(empty[T]) { (head, tail) => ! ! ! Not Eval’d Not Eval’d ! Not Eval’d ! cons(f(head), tail) }
  • 91.
    Returning Streams def map[T](f:A => T): Stream[T] = foldRight(empty[T]) { (head, tail) => ! ! ! Not Eval’d Not Eval’d ! Not Eval’d ! cons(f(head), tail) } Jeez, does this code even do anything!?
  • 92.
  • 93.
    Let’s find Out... Stream(1,2, 3, 4, 5) map { i => println(s"map -> $i") i * i }
  • 94.
    Let’s find Out... Stream(1,2, 3, 4, 5) map { i => println(s"map -> $i") i * i } Which prints... def map[T](f: A => T): Stream[T] = foldRight(empty[T]) { (head, tail) => cons(f(head), tail) } Remember that... and... def foldRight[B](z: => B)(f: (A, => B) => B): B def cons[A](hd: => A, tl: => Stream[A]): Stream[A] and...
  • 95.
  • 96.
  • 97.
    Putting it AllTogether val s = Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 }
  • 98.
    Putting it AllTogether val s = Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 } Prints nothing. OK
  • 99.
    Putting it AllTogether val s = Stream(1 to 10) map { i => println(s”stream -> map $i”) i + 10 } filter { i => println(s”stream -> filter $i”) i % 2 == 0 } def toList: List[A] = uncons match { case None => Nil case Some((h, t)) => h :: t.toList } Prints nothing. OK add toList()
  • 100.
  • 101.
  • 102.
    Evaluating val numList =s.toList // // // // // // // stream stream stream stream ... stream stream -> -> -> -> map 1 filter 11 map 2 filter 12 -> map 10 -> filter 20
  • 103.
    Evaluating val numList =s.toList def take(n: Int): Stream[A] = uncons match { case Some((h, t)) if n > 0 => cons(h, t.take(n - 1)) case _ => empty } // // // // // // // stream stream stream stream ... stream stream -> -> -> -> map 1 filter 11 map 2 filter 12 -> map 10 -> filter 20
  • 104.
    Evaluating val numList =s.toList def take(n: Int): Stream[A] = uncons match { case Some((h, t)) if n > 0 => cons(h, t.take(n - 1)) case _ => empty } val numList = s.take(1).toList // // // // // // // stream stream stream stream ... stream stream -> -> -> -> map 1 filter 11 map 2 filter 12 -> map 10 -> filter 20
  • 105.
    Evaluating val numList =s.toList def take(n: Int): Stream[A] = uncons match { case Some((h, t)) if n > 0 => cons(h, t.take(n - 1)) case _ => empty } val numList = s.take(1).toList // // // // // // // stream stream stream stream ... stream stream // // // // -> -> -> -> map 1 filter 11 map 2 filter 12 -> map 10 -> filter 20 stream stream stream stream -> -> -> -> map 1 filter 11 map 2 filter 12
  • 106.
  • 107.
    It’s All AboutFunctions Functions hold the values
  • 108.
    It’s All AboutFunctions Functions hold the values Higher order functions pile functions on functions
  • 109.
    It’s All AboutFunctions Functions hold the values Higher order functions pile functions on functions Even functions like take() merely store functions
  • 110.
    It’s All AboutFunctions Functions hold the values Higher order functions pile functions on functions Even functions like take() merely store functions Nothing “real” happens until you need it to happen
  • 111.
    It’s All AboutFunctions Functions hold the values Higher order functions pile functions on functions Even functions like take() merely store functions Nothing “real” happens until you need it to happen ! y in h S
  • 112.
    ms rea St ss yA az L i Brought to you SincereCouch Potato em s Derek Wyatt Twitter: @derekwyatt Email: derek@derekwyatt.org