1 of 63

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 of 63

2

API

DESIGN

JAVA 8

PROGRAMMER

3 of 63

3

GENERIC

COLLECTIONS

PROGRAMMER

JDK 1.0 (1996)

4 of 63

4

5 of 63

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� }

6 of 63

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� }

7 of 63

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)

8 of 63

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?!?!?!

9 of 63

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 of 63

10

2004-

11 of 63

11

1990-

12 of 63

12

PROGRAMMER

GENERIC

COLLECTIONS

JAVA 1.5

13 of 63

Can we abstract away

the differences?

13

abstract class StringListcase class Node(h: String, t: StringList)

extends IntList

case class Empty() extends StringList

abstract class IntListcase class Node(h: Int, t: IntList)

extends IntList

case class Empty() extends IntList

abstract class BoolListcase class Node(h: Boolean, t: BoolList)

extends BoolList

case class Empty() extends BoolList

abstract class StringListcase class Node(h: String, t: StringList)

extends IntList

case class Empty() extends StringList

abstract class IntListcase class Node(h: Int, t: IntList)

extends IntList

case class Empty() extends IntList

abstract class BoolListcase class Node(h: Boolean, t: BoolList)

extends BoolList

case class Empty() extends BoolList

14 of 63

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

15 of 63

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 of 63

16

17 of 63

17

API

DESIGN

JAVA 8

18 of 63

Monolythic code

18

def echoConsole(): String = {val msg: String = readLine()� println(msg)� msg�}

def echoFiles(): String =Console.withIn(in){Console.withOut(out){...}

}�

19 of 63

APIs to the rescue!

19

trait IO{def read(): Stringdef write(msg: String): Unit}

def echo()(io: IO): String = {val msg: String = io.read()� io.write(msg)� msg�}

ABSTRACT INTERFACE

20 of 63

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)

21 of 63

APIs support modularity!

21

trait IO{def write(msg: String): Unitdef 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 of 63

22

22

LOGIC

INTERPRETERS

INT.

...

INT.

INT.

INT.

INT.

INT.

23 of 63

23

24 of 63

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.

25 of 63

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.

26 of 63

We change the API!

26

trait IO{def read(): Future[String]

def write(a: A): Future[Unit]}

INT.

PROG.

IMP.

COMP.

27 of 63

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.Futureimport ExecutionContext.Implicits.globaldef echo(io: IO): Future[String] =� io.read().flatMap{ msg => � io.write(msg).flatMap{ _ =>Future.successful(msg)}}

INT.

PROG.

IMP.

COMP.

28 of 63

28

LOGIC

INT.

...

INT.

INT.

INT.

INT.

INT.

INTERPRETERS

29 of 63

What if we want to manage our dependencies explicitly?

29

case class IOFiles(

in: FileDescriptor, out: FileDescriptor)

trait IO{def read(): IOFiles => Stringdef write(a: A): IOFiles => Unit}

30 of 63

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 of 63

31

trait IO{def read(): IOFiles => Stringdef 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(): Stringdef write(msg: String): Unit}

trait IO{def read(): IOFiles => Stringdef 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(): Stringdef write(msg: String): Unit}

Can we abstract away

the differences?

32 of 63

Yes, we can! With higher-kinded generics

32

33 of 63

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.

34 of 63

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 => Stringdef write(msg: String): IOFiles => Unit}

type FileReader[T] = IOFiles => Ttype 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(): Stringdef write(msg: String): Unit}

type Id[T] = Ttype ConsoleIO = IO[Id]

INT.

PROG.

IMP.

COMP.

COMP.

35 of 63

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 of 63

36

INT.

PROG.

IMP.

COMP.

37 of 63

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�}

38 of 63

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

39 of 63

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

40 of 63

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)})})

41 of 63

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”

42 of 63

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)

43 of 63

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!!!!

44 of 63

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

45 of 63

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 of 63

46

LOGIC

INTERPRETERS

...

TYPE

CLASS

TYPE

CLASS

TYPE

CLASS

TYPE

CLASS

TYPE

CLASS

TYPE

CLASS

Monads

Declarat. functions

47 of 63

What have we done?

Deal with problems

  • Being forced to rewrite our business logic, even if it didn’t change at all!
    • Being unable to package our business logic
    • Being unable to reuse anything
  • Doing testing with mocking libraries
  • Doing dependency injection with awkward libraries or design patterns
  • etc.

47

48 of 63

How did we deal with them?

  • Following the API-based approach OO programmers are used to, i.e. writing functions against APIs
  • Although APIs are no longer abstract interfaces, but generic ones
  • And programs have to be composed using additional APIs of imperative combinators

48

49 of 63

Come on everybody! What are monads?

49

50 of 63

Eventually, we ended up in lambda world

  • Implementing pure functions, or rather declarative functions that return programs
  • Programs which are written in a language defined through type classes
  • Some of which will be domain-dependent (IO, WebService, MyStorage, …), and other generic ones (Monad, Applicative, etc.)

50

51 of 63

51

ADTs/GADTs,

Free/Eff monad, Natural trans.,

Catamorphisms,

Coproducts,

...

Although not in the lambda world … most difficult to understand

52 of 63

52

53 of 63

53

WRONG!

RIGHT!

54 of 63

Scala gives you what we need … at a reasonable cost

  • Higher-kinded generics and implicits, essential to model type classes
  • A mature ecosystem of libraries for functional programming (scalaz, typelevel.org)
  • Full compatibility with the Java ecosystem, and Javascript!
  • A growing community of passionate programmers

54

55 of 63

Learn from great books

Functional programming in Scala.

Paul Chiusano, Rúnar Bjarnason

Haskell programming from first principles. Christopher Allen, Julie Moronuki

55

56 of 63

Blog posts, libraries, etc.

  • From Object Algebras to Finally Tagless Interpreters by Oleksandr Manzyuk
  • Typed final (tagless-final) style by Oleg Kiselyov
  • Haskell’s Monad transformer library (MTL style) by Edward Kmett
  • Alternatives to GADTs in Scala, by Paul Chiusano
  • EDSLs as functions, by Adelbert Chang

56

57 of 63

57

blog.hablapps.com

58 of 63

58

59 of 63

Meetup groups

59

60 of 63

60

61 of 63

61

62 of 63

62

63 of 63

THANKS FOR YOUR ATTENTION!

63

@hablapps

http://www.hablapps.com