slide /
6th Conference on Lisp and Functional Programming, 1992
return style :(
public class Foo {
public Bar getBar();
}
public class Bar {
public Baz getBaz();
}
public class Baz {
public int compute();
}
|
public Integer compute(Foo foo) {
if (foo == null)
return null;
Bar bar = foo.getBar();
if (bar == null)
return null;
Baz baz = bar.getBaz();
if (baz == null)
return null;
return baz.compute();
}
|
if style :(
public class Foo {
public Bar getBar();
}
public class Bar {
public Baz getBaz();
}
public class Baz {
public int compute();
}
|
public Integer compute(Foo foo) {
if (foo != null) {
Bar bar = foo.getBar();
if (bar != null) {
Baz baz = bar.getBaz();
if (baz != null)
return baz.compute();
else
return null;
}
else
return null;
}
else
return null;
}
|
class Foo {
def bar: Bar
}
class Bar {
def baz: Baz
}
class Baz {
def compute: Int
}
|
def compute(foo: Foo): Int =
if (foo != null) {
val bar = foo.bar
if (bar != null) {
val baz = bar.baz
if (baz != null)
baz.compute
else
null
}
else
null
}
else
null
|
trait Monad[A] {
def map[B](f: A => B): Monad[B]
def flatMap[B](f: A => Monad[B]): Monad[B]
}
Note: there is no single, special Monad base trait!
Provides a standard interface for composing and sequencing operations on some contained value(s)
map: Applies a "regular" function to the contained value(s)flatMap: Applies a "monadic" function to the contained value(s)Option monadOption monadsealed trait Option[A] case class Some[A](a: A) extends Option[A] case class None[A] extends Option[A]
Option monad
sealed trait Option[A] {
def map[B](f: A => B): Option[B]
def flatMap[B](f: A => Option[B]): Option[B]
}
case class Some[A](a: A) extends Option[A]
case class None[A] extends Option[A]
Option monad
sealed trait Option[A] {
def map[B](f: A => B): Option[B]
def flatMap[B](f: A => Option[B]): Option[B]
}
case class Some[A](a: A) extends Option[A] {
def map[B](f: A => B): Option[B] = new Some(f(a))
def flatMap[B](f: A => Option[B]): Option[B] = f(a)
}
case class None[A] extends Option[A]
Option monad
sealed trait Option[A] {
def map[B](f: A => B): Option[B]
def flatMap[B](f: A => Option[B]): Option[B]
}
case class Some[A](a: A) extends Option[A] {
def map[B](f: A => B): Option[B] = new Some(f(a))
def flatMap[B](f: A => Option[B]): Option[B] = f(a)
}
case class None[A] extends Option[A] {
def map[B](f: A => B): Option[B] = new None
def flatMap[B](f: A => Option[B]): Option[B] = new None
}
Note: this differs from the standard scala.Option
Option monad
class Foo { def bar: Option[Bar] }
class Bar { def baz: Option[Baz] }
class Baz { def compute: Int }
def compute(maybeFoo: Option[Foo]): Option[Int] = // ???
Option monad
class Foo { def bar: Option[Bar] }
class Bar { def baz: Option[Baz] }
class Baz { def compute: Int }
def computeBaz(baz: Baz) = // ???
def computeBar(bar: Bar) = // ???
def computeFoo(foo: Foo) = // ???
def compute(maybeFoo: Option[Foo]): Option[Int] = // ???
Option monad
class Foo { def bar: Option[Bar] }
class Bar { def baz: Option[Baz] }
class Baz { def compute: Int }
def computeBaz(baz: Baz): Int =
baz.compute
def computeBar(bar: Bar) = // ???
def computeFoo(foo: Foo) = // ???
def compute(maybeFoo: Option[Foo]): Option[Int] = // ???
Option monad
class Foo { def bar: Option[Bar] }
class Bar { def baz: Option[Baz] }
class Baz { def compute: Int }
def computeBaz(baz: Baz): Int =
baz.compute
def computeBar(bar: Bar): Option[Int] =
bar.baz.map { computeBaz }
def computeFoo(foo: Foo) = // ???
def compute(maybeFoo: Option[Foo]): Option[Int] = // ???
Option monad
class Foo { def bar: Option[Bar] }
class Bar { def baz: Option[Baz] }
class Baz { def compute: Int }
def computeBaz(baz: Baz): Int =
baz.compute
def computeBar(bar: Bar): Option[Int] =
bar.baz.map { computeBaz }
def computeFoo(foo: Foo): Option[Int] =
foo.bar.flatMap { computeBar }
def compute(maybeFoo: Option[Foo]): Option[Int] = // ???
Option monad
class Foo { def bar: Option[Bar] }
class Bar { def baz: Option[Baz] }
class Baz { def compute: Int }
def computeBaz(baz: Baz): Int =
baz.compute
def computeBar(bar: Bar): Option[Int] =
bar.baz.map { computeBaz }
def computeFoo(foo: Foo): Option[Int] =
foo.bar.flatMap { computeBar }
def compute(maybeFoo: Option[Foo]): Option[Int] =
maybeFoo.flatMap { computeFoo }
Option monad
class Foo { def bar: Option[Bar] }
class Bar { def baz: Option[Baz] }
class Baz { def compute: Int }
def compute(maybeFoo: Option[Foo]): Option[Int] =
maybeFoo.flatMap { foo =>
foo.bar.flatMap { bar =>
bar.baz.map { baz =>
baz.compute
}
}
}
public class Foo { public Bar getBar() throws Exception; }
public class Bar { public Baz getBaz() throws Exception; }
public class Baz { public int compute() throws Exception; }
public Integer compute(Foo foo) throws Exception {
return foo.getBar().getBaz().compute();
}
public class Foo { public Bar getBar() throws GetBarException; }
public class Bar { public Baz getBaz() throws GetBazException; }
public class Baz { public int compute() throws ComputeException; }
public Integer compute(Foo foo) throws ComputeException {
try {
foo.getBar().getBaz().compute();
}
catch (GetBarException e) {
throw new ComputeException(e);
}
catch (GetBazException e) {
throw new ComputeException(e);
}
}
Validation monadValidation monad
sealed trait Validation[E, A] {
def map[B](f: A => B): Validation[E, B]
def flatMap[B](f: A => Validation[E, B]): Validation[E, B]
def liftFail[F](f: E => F): Validation[F, A] // unrelated to monads!
}
case class Success[E, A](a: A) extends Validation[E, A] {
def map[B](f: A => B): Validation[E, B] = new Success(f(a))
def flatMap[B](f: A => Validation[E, B]): Validation[E, B] = f(a)
def liftFail[F](f: E => F): Validation[F, A] = new Success(a)
}
case class Failure[E, A](e: E) extends Validation[E, A] {
def map[B](f: A => B): Validation[E, B] = new Failure(e)
def flatMap[B](f: A => Validation[E, B]): Validation[E, B] = new Failure(e)
def liftFail[F](f: E => F): Validation[F, A] = new Failure(f(e))
}
Note: based loosely on Validation from the scalaz library (https://github.com/scalaz/scalaz).
Validation monad
class Foo { def bar: Validation[BarException, Bar] }
class Bar { def baz: Validation[BazException, Baz] }
class Baz { def compute: Validation[ComputeException, Int] }
def compute(foo: Foo): Validation[ComputeException, Int] =
foo.bar.liftFail { new ComputeException(_) }.flatMap { bar =>
bar.baz.liftFail { new ComputeException(_) }.flatMap { baz =>
baz.compute
}
}
Option.
public class Foo {
public List<Bar> getBars();
}
public class Bar {
public List<Baz> getBazs();
}
public class Baz {
public List<Integer> computeAll();
}
|
public List<Integer> computeAll(List<Foo> foos) {
List<Integer> results = new ArrayList<Integer>();
for (Foo foo: foos) {
for (Bar bar: foo.getBars()) {
for (Baz baz: bar.getBazs()) {
results.addAll(baz.computeAll());
}
}
}
return results;
}
|
ArrayList...
class Foo { def bars: List[Bar] }
class Bar { def bazs: List[Baz] }
class Baz { def computeAll: List[Int] }
def computeAll(foos: List[Foo]): List[Int] =
for {
foo <- foos
bar <- foo.bars
baz <- bar.bazs
results <- baz.computeAll
} yield results
for-comprehensions
for { p0 <- e0 } yield e |
e0.map { p0 => e } |
for {
p0 <- e0
p1 <- e1
...
pn <- en
} yield e
|
e0.flatMap { p0 =>
for {
p1 <- e1
...
pn <- en
} yield e
} |
for-comprehensions operate on any monad.map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
for {
foo <- foos
bar <- foo.bars
baz <- bar.bazs
results <- baz.computeAll
} yield results
map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
foos.flatMap { foo =>
for {
bar <- foo.bars
baz <- bar.bazs
results <- baz.computeAll
} yield results
}
map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
foos.flatMap { foo =>
for {
bar <- foo.bars
baz <- bar.bazs
results <- baz.computeAll
} yield results
}
map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
foos.flatMap { foo =>
foo.bars.flatMap { bar =>
for {
baz <- bar.bazs
results <- baz.computeAll
} yield results
}
}
map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
foos.flatMap { foo =>
foo.bars.flatMap { bar =>
for {
baz <- bar.bazs
results <- baz.computeAll
} yield results
}
}
map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
foos.flatMap { foo =>
foo.bars.flatMap { bar =>
bar.bazs.flatMap { baz =>
for { results <- baz.computeAll } yield results
}
}
}
map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
foos.flatMap { foo =>
foo.bars.flatMap { bar =>
bar.bazs.flatMap { baz =>
for { results <- baz.computeAll } yield results
}
}
}
map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
foos.flatMap { foo =>
foo.bars.flatMap { bar =>
bar.bazs.flatMap { baz =>
baz.computeAll.map { results => results }
}
}
}
map and flatMap
def computeAll(foos: List[Foo]): List[Int] =
foos.flatMap { foo =>
foo.bars.flatMap { bar =>
bar.bazs.flatMap { baz =>
baz.computeAll
}
}
}
for-comprehensions
def compute(maybeFoo: Option[Foo]): Option[Int] =
maybeFoo.flatMap { foo =>
foo.bar.flatMap { bar =>
bar.baz.map { baz => baz.compute }
}
}
for-comprehensions
def compute(maybeFoo: Option[Foo]): Option[Int] =
maybeFoo.flatMap { foo =>
foo.bar.flatMap { bar =>
for { baz <- bar.baz } yield baz.compute
}
}
for-comprehensions
def compute(maybeFoo: Option[Foo]): Option[Int] =
maybeFoo.flatMap { foo =>
foo.bar.flatMap { bar =>
for { baz <- bar.baz } yield baz.compute
}
}
for-comprehensions
def compute(maybeFoo: Option[Foo]): Option[Int] =
maybeFoo.flatMap { foo =>
for {
bar <- foo.bar
baz <- bar.baz
} yield baz.compute
}
for-comprehensions
def compute(maybeFoo: Option[Foo]): Option[Int] =
maybeFoo.flatMap { foo =>
for {
bar <- foo.bar
baz <- bar.baz
} yield baz.compute
}
for-comprehensions
def compute(maybeFoo: Option[Foo]): Option[Int] =
for {
foo <- maybeFoo
bar <- foo.bar
baz <- bar.baz
} yield baz.compute
Before (Java)
if (foo != null) {
Bar bar = foo.getBar();
if (bar != null) {
Baz baz = bar.getBaz();
if (baz != null)
return baz.compute();
else
return null;
}
else
return null;
}
else
return null;
Conflation of concerns: "coping with failure" is intertwined with the computation. |
After (Scala)
for {
foo <- maybeFoo
bar <- foo.bar
baz <- bar.baz
} yield baz.compute
Separation of concerns: the |
for-comprehensions
def compute(foo: Foo): Validation[ComputeException, Int] =
foo.bar.liftFail { new ComputeException(_) }.flatMap { bar =>
bar.baz.liftFail { new ComputeException(_) }.flatMap { baz =>
baz.compute
}
}
for-comprehensions
def compute(foo: Foo): Validation[ComputeException, Int] =
foo.bar.liftFail { new ComputeException(_) }.flatMap { bar =>
bar.baz.liftFail { new ComputeException(_) }.flatMap { baz =>
baz.compute.map { result => result }
}
}
for-comprehensions
def compute(foo: Foo): Validation[ComputeException, Int] =
for {
bar <- foo.bar.liftFail { new ComputeException(_) }
baz <- bar.baz.liftFail { new ComputeException(_) }
result <- baz.compute
} yield result
Option example
def compute(maybeFoo: Option[Foo]): Option[Int] =
for {
foo <- maybeFoo
bar <- foo.bar
baz <- bar.baz
} yield baz.compute
Monads capture policies for function application and composition, in map and flatMap
for-comprehensions provide a sequential syntax, on top of map and flatMap
Option monad makes the possibility of missing data explicit in the type system, while hiding the boilerplate "if non-null" logicValidation monad (from scalaz) makes the possibility of errors explicit in the type system, while hiding the boilerplate "try/catch" logicList monad makes nondeterminism explicit in the type system, while hiding the boilerplate of nested for-loopsState and Undo monads (from scalaz) make the presence of mutable state explicit in the type system, while hiding the boilerplate propogation of state changesPromise monad (from scalaz) makes asynchronous computation explicit in the type system, while hiding the boilerplate threading logicTransaction monad (non-standard) makes transactionality explicit in the type system, while hiding the boilerplate of invoking rollbacks... and more.