All roads lead to …
lambda
Habla Computing
Juan Manuel Serrano, @juanshac
juanmanuel.serrano@hablapps.com
In collaboration with:
Javier Fuentes, @javifdev
Jesús López, @jeslg
2
API
DESIGN
JAVA 8
PROGRAMMER
3
GENERIC
COLLECTIONS
PROGRAMMER
JDK 1.0 (1996)
4
We couldn’t write generic collections
5
abstract class IntList{� def head: Int � }� � case class Empty() extends IntList{� def head = throw new Exception("...")� }�
case class Node(h: Int, t: IntList)
extends IntList{� def head = h� }
And had to copy & paste
6
abstract class StringList{� def head: String � }� � case class Empty() extends StringList{� def head = throw new Exception("...")� }�
case class Node(h: String, t: StringList)
extends StringList{� def head = h� }
Subtype polymorphism to the “rescue”!
7
abstract class List{� def head: Any � }� � case class Empty() extends List{� def head = throw new Exception("...")� }�
case class Node(h: Any, t: List)
extends List{� def head = h� }
JDK 1.0 (1996)
It works!
8
val int: Any = l1.head
val intAsTrueInt = l1.head.asInstanceOf[Int]
val s1: String = l2.head.asInstanceOf[String]
val l1: List =
List.Node(1,List.Node(2,List.Empty()))�val l2: List =
List.Node("1",List.Node("2",List.Empty()))
val s2: String =
l1.head.asInstanceOf[String]
Really?!?!?!
GJ language extension
Added in JDK 1.5 (2004)
9
Left to right: Philip Wadler, Martin Odersky, Gilad Bracha, Dave Stoutamire
http://homepages.inf.ed.ac.uk/wadler/gj/
10
2004-
11
1990-
12
PROGRAMMER
GENERIC
COLLECTIONS
JAVA 1.5
Can we abstract away
the differences?
13
abstract class StringList�case class Node(h: String, t: StringList)
extends IntList
case class Empty() extends StringList
abstract class IntList�case class Node(h: Int, t: IntList)
extends IntList
case class Empty() extends IntList
abstract class BoolList�case class Node(h: Boolean, t: BoolList)
extends BoolList
case class Empty() extends BoolList
abstract class StringList�case class Node(h: String, t: StringList)
extends IntList
case class Empty() extends StringList
abstract class IntList�case class Node(h: Int, t: IntList)
extends IntList
case class Empty() extends IntList
abstract class BoolList�case class Node(h: Boolean, t: BoolList)
extends BoolList
case class Empty() extends BoolList
Parametric polymorphism to the rescue!
14
abstract class StringList{� def head: String � }� � case class Empty() extends StringList{� def head = throw new Exception("...")� }�
case class Node(h: String, t: StringList)
extends StringList{� def head = h� }
abstract class List[A]{� def head: A � }� � case class Empty[A]() extends List[A]{� def head = throw new Exception("...")� }�
case class Node[A](h: A, t: List[A])
extends List[A]{� def head = h� }
JAVA 1.5
It works!
15
val int: Int = l1.head
val s1: String = l2.head
val l1: List[Int] =
List.Node(1,List.Node(2,List.Empty()))�val l2: List[String] =
List.Node("1",List.Node("2",List.Empty()))
val s2: String = l1.head
It really works!
DOESN’T COMPILE!
16
17
API
DESIGN
JAVA 8
Monolythic code
18
def echoConsole(): String = {� val msg: String = readLine()� println(msg)� msg�}
def echoFiles(): String = � Console.withIn(in){� Console.withOut(out){� ...� }
}�
APIs to the rescue!
19
trait IO{� def read(): String� def write(msg: String): Unit�}
def echo()(io: IO): String = {� val msg: String = io.read()� io.write(msg)� msg�}
ABSTRACT INTERFACE
Encapsulation and composition
20
object ConsoleIO extends IO{� def read(): String =
scala.io.StdIn.readLine()� def write(msg: String): Unit =
Console.println(msg)�}
def echoConsole() = echo(ConsoleIO)
object FileIO extends IO{� def read(): String = ???� def write(msg: String): Unit =
???�}
def echoFiles() = echo(FileIO)
APIs support modularity!
21
trait IO{� def write(msg: String): Unit� def read(): String�}
def echo()(io: IO): String = {� val msg: String = io.read� io.write(msg)
msg�}
object ConsoleIO extends IO{� def write(msg: String) =
println(msg)� def read =
readLine�}
def consoleEcho: String = � echo(ConsoleIO)�
INTERFACE
IMPLEMENTATION
COMPOSITION
API/INTERFACE
API PROGRAM
INT.
PROG.
IMP.
COMP.
22
22
LOGIC
INTERPRETERS
INT.
...
INT.
INT.
INT.
INT.
INT.
23
But … what if I want to implement
our API asynchronously?
24
object AsyncIO extends IO{�� def read(): String = {� val futureMsg: Future[String] = ???� ...� }�
...�}
object AsyncIO extends IO{�� def read(): String = {� val futureMsg: Future[String] = ???� ...� }�
...�}
object AsyncIO extends IO{�� def read(): String = {� val futureMsg: Future[String] = ???� Await.result(futureMsg, 1 second)� }�
...�}
We can’t block …
INT.
PROG.
IMP.
COMP.
Blocking doesn’t make sense, so …
25
object AsyncIO extends IO{� def read(): Future[String] = {� ???� }�
def write(a: A): Future[Unit] = {� ???� }�}
INT.
PROG.
IMP.
COMP.
We change the API!
26
trait IO{� def read(): Future[String]
def write(a: A): Future[Unit]�}
INT.
PROG.
IMP.
COMP.
And all the business logic!
27
def echo(io: IO): String = {� val msg: String = io.read()� io.write(msg)� msg�}
import scala.concurrent.ExecutionContext
import scala.concurrent.Future�import ExecutionContext.Implicits.global��def echo(io: IO): Future[String] =� io.read().flatMap{ msg => � io.write(msg).flatMap{ _ => � Future.successful(msg)� }� }
INT.
PROG.
IMP.
COMP.
28
LOGIC
INT.
...
INT.
INT.
INT.
INT.
INT.
INTERPRETERS
What if we want to manage our dependencies explicitly?
29
case class IOFiles(
in: FileDescriptor, out: FileDescriptor)
trait IO{� def read(): IOFiles => String� def write(a: A): IOFiles => Unit�}
What if we want to do unit testing in a declarative way?
30
case class IOState(
in: List[String], out: List[String])
trait IO{� def read(): IOState=>(IOState, String)� def write(a: String):
IOState=>(IOState, Unit)�}
31
trait IO{� def read(): IOFiles => String� def write(msg: String): IOFiles => Unit�}
trait IO{� def read(): IOState => (IOState, String)� def write(msg: String): IOState => (IOState, Unit)�}
trait IO{� def read(): Future[String]� def write(msg: String): Future[Unit]�}
trait IO{� def read(): String� def write(msg: String): Unit�}
trait IO{� def read(): IOFiles => String� def write(msg: String): IOFiles => Unit�}
trait IO{� def read(): IOState => (IOState, String)� def write(msg: String): IOState => (IOState, Unit)�}
trait IO{� def read(): Future[String]� def write(msg: String): Future[Unit]�}
trait IO{� def read(): String� def write(msg: String): Unit�}
Can we abstract away
the differences?
Yes, we can! With higher-kinded generics
32
Parametric polymorpohism
to the rescue!
33
trait IO {� def read(): Future[String]� def write(msg: String): Future[Unit]�}
trait IO[P[_]]{� def read(): P[String]� def write(msg: String): P[Unit]�}
INT.
PROG.
IMP.
COMP.
COMP.
Our API can accommodate
any interpretation!
34
trait AsynchIO{� def read(): Future[String]� def write(msg: String): Future[Unit]�}
type AsynchIO = IO[Future]�
trait FileIO{� def read(): IOFiles => String� def write(msg: String): IOFiles => Unit�}
type FileReader[T] = IOFiles => T�type FileIO = IO[FileReader]
trait IOAction{� def read(): IOState => (IOState, String)� def write(msg: String): IOState => (IOState, Unit)�}
type IOAction[T] = IOState => (IOState,T)�type TestingIO = IO[IOAction]
trait ConsoleIO{� def read(): String� def write(msg: String): Unit�}
type Id[T] = T�type ConsoleIO = IO[Id]
INT.
PROG.
IMP.
COMP.
COMP.
Instantiating this kind of interface
arbitrarily is easy!
35
object ConsoleIO extends IO {� def read(): String = readLine()� def write(msg: String): Unit = println(msg)�}
object ConsoleIO extends IO[Id]{� def read(): Id[String] = readLine()� def write(msg: String): Unit = println(msg)�}
INT.
PROG.
IMP.
COMP.
COMP.
object AsynchIO extends IO {� def read(): Future[String] = ???� def write(msg: String): Future[Unit] = ???�}
object AsynchIO extends IO[Future]{� def read(): Future[String] = ???� def write(msg: String): Future[Unit] = ???�}
36
INT.
PROG.
IMP.
COMP.
Which problem do we have?
37
def echo()(io: IO): String = {� val msg: String = io.read()� io.write(msg)� msg�}
INT.
PROG.
IMP.
COMP.
: String
COMPILATION
ERROR!
def echo()(io: IO[Id]): String = {� val msg: String = io.read()� io.write(msg)� msg�}
How do we sequence
our instructions?
38
def echo ()(io: IO ): String = {� val msg: String = io.read()� io.write(msg)� msg�}
def echo[P[_]]()(io: IO[P]): P[String] = {� val msg: String = io.read()� io.write(msg)� msg�}
COMPILATION
ERRORS!
INT.
PROG.
IMP.
COMP.
: P[String]
: String
Should we run somehow
our programs P? Rather not
39
def echo[P[_]]()(io: IO[P]): P[String] = {� val msg: String = io.read().run� io.write(msg).run� msg�}
COMPILATION
ERROR!
INT.
PROG.
IMP.
COMP.
: String
We require a new API of imperative
combinators
40
def echo[P[_]]()(io: IO[P]):
P[String] = {� val msg: String = io.read() ;� val _ : Unit = io.write(msg) ;� return msg�}
INT.
PROG.
IMP.
COMP.
def echo[P[_]]()(io: IO[P], m: Imp[P]):
P[String] = � ???
�
def echo[P[_]]()(io: IO[P], m: Imp[P]):
P[String] = � m.doAndThen(io.read(),{ msg: String => � ???
� })
def echo[P[_]]()(io: IO[P], m: Imp[P]):
P[String] = � m.doAndThen(io.read(),{ msg: String => � m.doAndThen(io.write(msg),{_ : Unit => � ???� })� })
def echo[P[_]]()(io: IO[P], m: Imp[P]):
P[String] = � m.doAndThen(io.read(),{ msg: String => � m.doAndThen(io.write(msg),{_ : Unit => � m.returns(msg)� })� })
And the actual name of this API of
imperative combinators is …
41
INT.
PROG.
IMP.
COMP.
trait ImperativeCombinators[P[_]]{� def doAndThen[A,B](p: P[A],
f: A => P[B]): P[B]� def returns[A](a: A): P[A]�}
trait Monad[P[_]]{� def doAndThen[A,B](p: P[A],
f: A => P[B]): P[B]� def returns[A](a: A): P[A]�}
trait Monad[P[_]]{� def flatMap[A,B](
p: P[A])(f: A => P[B]): P[B]� def pure[A](a: A): P[A]�}
“Monads are APIs to write imperative programs”, or
“Monads define the class of imperative languages”
Reuse the echo program across
arbitrary interpretations like a boss!
42
INT.
PROG.
IMP.
COMP.
def consoleEcho: String =
echo[Id](ConsoleIO, IdMonad)
def asynchEcho: Future[String] =
echo[Future](AsynchIO, FutureMonad)
Pretty modular and reusable
programs and APIs, but …
43
def echo()(io: IO): String = {� val msg = io.read()� io.write(msg)� msg�}
INT.
PROG.
IMP.
COMP.
def echo[P[_]]()(io: IO[P], m: Imp[P]):
P[String] = � m.doAndThen(io.read(),{ msg: String => � m.doAndThen(io.write(msg),{_ : Unit => � m.returns(msg)� })� })
THIS IS UGLY!!!!
With a spoonful of
44
INT.
PROG.
IMP.
COMP.
def echo[P[_]: IO: Monad](): P[String] =
for {� msg <- read()� _ <- write(msg)� } yield msg
Context bounds
For-comprehensions
def read[P[_]]()(implicit IO: IO[P]):
P[String] = IO.read()
Type class syntax
And we could even write
a monad macro, … just for fun
45
def echo()(io: IO): String = {� val msg: String = io.read()� io.write(msg)� msg�}
INT.
PROG.
IMP.
COMP.
import org.hablapps.gist.monad, monad._
def echo[P[_]: IO: Monad](): P[String] = monad{
val msg: String = read().run� write(msg).run� msg�}
46
LOGIC
INTERPRETERS
...
TYPE
CLASS
TYPE
CLASS
TYPE
CLASS
TYPE
CLASS
TYPE
CLASS
TYPE
CLASS
Monads
Declarat. functions
What have we done?
Deal with problems
47
How did we deal with them?
48
Come on everybody! What are monads?
49
Eventually, we ended up in lambda world
50
51
ADTs/GADTs,
Free/Eff monad, Natural trans.,
Catamorphisms,
Coproducts,
...
Although not in the lambda world … most difficult to understand
52
53
WRONG!
RIGHT!
Scala gives you what we need … at a reasonable cost
54
Learn from great books
Functional programming in Scala.
Paul Chiusano, Rúnar Bjarnason
Haskell programming from first principles. Christopher Allen, Julie Moronuki
55
Blog posts, libraries, etc.
56
57
blog.hablapps.com
58
Meetup groups
59
60
61
62
THANKS FOR YOUR ATTENTION!
63
@hablapps
http://www.hablapps.com