Programs as Values JDBC Programming with doobie Rob Norris Gemini - - PowerPoint PPT Presentation

programs as values
SMART_READER_LITE
LIVE PREVIEW

Programs as Values JDBC Programming with doobie Rob Norris Gemini - - PowerPoint PPT Presentation

Programs as Values JDBC Programming with doobie Rob Norris Gemini Observatory Programs as Values JDBC Programming with doobie Rob Norris Gemini Observatory What's this about? This is a talk about doobie , a pure functional database


slide-1
SLIDE 1

Programs as Values

JDBC Programming with doobie

Rob Norris • Gemini Observatory

slide-2
SLIDE 2

Programs as Values

JDBC Programming with doobie

Rob Norris • Gemini Observatory

slide-3
SLIDE 3

What's this about?

This is a talk about doobie, a pure functional database access layer for Scala.

slide-4
SLIDE 4

What's this about?

This is a talk about doobie, a pure functional database access layer for Scala.

  • JDBC programming is terrible.
slide-5
SLIDE 5

What's this about?

This is a talk about doobie, a pure functional database access layer for Scala.

  • JDBC programming is terrible.
  • Free monads [over free functors] are awesome.
slide-6
SLIDE 6

What's this about?

This is a talk about doobie, a pure functional database access layer for Scala.

  • JDBC programming is terrible.
  • Free monads [over free functors] are awesome.
  • Think of programs as composable values, rather than

imperative processes.

slide-7
SLIDE 7

What's this about?

This is a talk about doobie, a pure functional database access layer for Scala.

  • JDBC programming is terrible.
  • Free monads [over free functors] are awesome.
  • Think of programs as composable values, rather than

imperative processes.

  • Lather, rinse, repeat. This is a great strategy for

making terrible APIs tolerable.

slide-8
SLIDE 8

The Problem

So what's wrong with this JDBC program?

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • def ¡getPerson(rs: ¡ResultSet): ¡Person ¡= ¡{ ¡

¡ ¡val ¡name ¡= ¡rs.getString(1) ¡ ¡ ¡val ¡age ¡ ¡= ¡rs.getInt(2) ¡ ¡ ¡Person(name, ¡age) ¡ } ¡

slide-9
SLIDE 9

The Problem

So what's wrong with this JDBC program?

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • def ¡getPerson(rs: ¡ResultSet): ¡Person ¡= ¡{ ¡

¡ ¡val ¡name ¡= ¡rs.getString(1) ¡ ¡ ¡val ¡age ¡ ¡= ¡rs.getInt(2) ¡ ¡ ¡Person(name, ¡age) ¡ } ¡

Managed Resource

slide-10
SLIDE 10

The Problem

So what's wrong with this JDBC program?

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • def ¡getPerson(rs: ¡ResultSet): ¡Person ¡= ¡{ ¡

¡ ¡val ¡name ¡= ¡rs.getString(1) ¡ ¡ ¡val ¡age ¡ ¡= ¡rs.getInt(2) ¡ ¡ ¡Person(name, ¡age) ¡ } ¡

Side-Effect Managed Resource

slide-11
SLIDE 11

The Problem

So what's wrong with this JDBC program?

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • def ¡getPerson(rs: ¡ResultSet): ¡Person ¡= ¡{ ¡

¡ ¡val ¡name ¡= ¡rs.getString(1) ¡ ¡ ¡val ¡age ¡ ¡= ¡rs.getInt(2) ¡ ¡ ¡Person(name, ¡age) ¡ } ¡

Side-Effect Managed Resource Composition?

slide-12
SLIDE 12

The Strategy

Here is our game plan:

slide-13
SLIDE 13

The Strategy

Here is our game plan:

  • Let's talk about primitive operations as values ...

these are the smallest meaningful programs.

slide-14
SLIDE 14

The Strategy

Here is our game plan:

  • Let's talk about primitive operations as values ...

these are the smallest meaningful programs.

  • Let's define rules for combining little programs to

make bigger programs.

slide-15
SLIDE 15

The Strategy

Here is our game plan:

  • Let's talk about primitive operations as values ...

these are the smallest meaningful programs.

  • Let's define rules for combining little programs to

make bigger programs.

  • Let's define an interpreter that consumes these

programs and performs actual work.

slide-16
SLIDE 16

Primitives

An algebra is just a set of objects, along with rules for manipulating

  • them. Here our objects are the set of primitive computations you can

perform with a JDBC ResultSet.

sealed ¡trait ¡ResultSetOp[A] ¡

  • case ¡object ¡Next ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡extends ¡ResultSetOp[Boolean] ¡

case ¡class ¡ ¡GetInt(i: ¡Int) ¡ ¡ ¡ ¡extends ¡ResultSetOp[Int] ¡ case ¡class ¡ ¡GetString(i: ¡Int) ¡extends ¡ResultSetOp[String] ¡ case ¡object ¡Close ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡extends ¡ResultSetOp[Unit] ¡ // ¡188 ¡more... ¡

slide-17
SLIDE 17

Primitives

An algebra is just a set of objects, along with rules for manipulating

  • them. Here our objects are the set of primitive computations you can

perform with a JDBC ResultSet.

sealed ¡trait ¡ResultSetOp[A] ¡

  • case ¡object ¡Next ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡extends ¡ResultSetOp[Boolean] ¡

case ¡class ¡ ¡GetInt(i: ¡Int) ¡ ¡ ¡ ¡extends ¡ResultSetOp[Int] ¡ case ¡class ¡ ¡GetString(i: ¡Int) ¡extends ¡ResultSetOp[String] ¡ case ¡object ¡Close ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡extends ¡ResultSetOp[Unit] ¡ // ¡188 ¡more... ¡

Constructor per Method

slide-18
SLIDE 18

Primitives

An algebra is just a set of objects, along with rules for manipulating

  • them. Here our objects are the set of primitive computations you can

perform with a JDBC ResultSet.

sealed ¡trait ¡ResultSetOp[A] ¡

  • case ¡object ¡Next ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡extends ¡ResultSetOp[Boolean] ¡

case ¡class ¡ ¡GetInt(i: ¡Int) ¡ ¡ ¡ ¡extends ¡ResultSetOp[Int] ¡ case ¡class ¡ ¡GetString(i: ¡Int) ¡extends ¡ResultSetOp[String] ¡ case ¡object ¡Close ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡extends ¡ResultSetOp[Unit] ¡ // ¡188 ¡more... ¡

Constructor per Method Parameterized

  • n Return Type
slide-19
SLIDE 19

Operations

slide-20
SLIDE 20

Operations

A B

slide-21
SLIDE 21

Operations

A B C

f

slide-22
SLIDE 22

Operations

A B C

f

D

f

slide-23
SLIDE 23

Operations

A B C

f

D

f

E E

f

slide-24
SLIDE 24

Operations

A B C

f

D

f

E E

f

F

slide-25
SLIDE 25

Operations

A B C

f

D

f

E E

f

F

unit

slide-26
SLIDE 26

Operations

A B C

f

D

f

E E

f

F

unit flatMap

slide-27
SLIDE 27

Operations

A B C

f

D

f

E E

f

F

unit flatMap map

slide-28
SLIDE 28

Operations

A B C

f

D

f

E E

f

F

unit flatMap map lift2

slide-29
SLIDE 29

Operations

A B C

f

D

f

E E

f

F

unit flatMap map lift2

Monad !

slide-30
SLIDE 30

If only…

If we had Monad[ResultSetOp] we could do this:

case class Person(name: String, age: Int)

  • val getPerson: ResultSetOp[Person] =

for { name <- GetString(1) age <- GetInt(2) } yield Person(name, age)

slide-31
SLIDE 31

If only…

If we had Monad[ResultSetOp] we could do this:

case class Person(name: String, age: Int)

  • val getPerson: ResultSetOp[Person] =

for { name <- GetString(1) age <- GetInt(2) } yield Person(name, age)

But we don't.

slide-32
SLIDE 32

Spare a Monad?

Fancy words. Please be seated.

slide-33
SLIDE 33

Spare a Monad?

Fancy words. Please be seated.

  • Free[F[_],?] is a monad for any functor F.
slide-34
SLIDE 34

Spare a Monad?

Fancy words. Please be seated.

  • Free[F[_],?] is a monad for any functor F.
  • Coyoneda[S[_],?] is a functor for any S at all.
slide-35
SLIDE 35

Spare a Monad?

Fancy words. Please be seated.

  • Free[F[_],?] is a monad for any functor F.
  • Coyoneda[S[_],?] is a functor for any S at all.
  • By substitution, Free[Coyoneda[S[_],?],?] is a

monad for any S at all.

slide-36
SLIDE 36

Spare a Monad?

Fancy words. Please be seated.

  • Free[F[_],?] is a monad for any functor F.
  • Coyoneda[S[_],?] is a functor for any S at all.
  • By substitution, Free[Coyoneda[S[_],?],?] is a

monad for any S at all.

  • Abbreviated as FreeC[S[_],?]
slide-37
SLIDE 37

Spare a Monad?

Fancy words. Please be seated.

  • Free[F[_],?] is a monad for any functor F.
  • Coyoneda[S[_],?] is a functor for any S at all.
  • By substitution, Free[Coyoneda[S[_],?],?] is a

monad for any S at all.

  • Abbreviated as FreeC[S[_],?]
  • And if S = ResultSetOp we now have a monad.
slide-38
SLIDE 38

Wait, what?

import scalaz.{ Free, Coyoneda } import scalaz.Free.{ FreeC, liftFC }

  • type ResultSetIO[A] = FreeC[ResultSetOp, A]
slide-39
SLIDE 39

Wait, what?

import scalaz.{ Free, Coyoneda } import scalaz.Free.{ FreeC, liftFC }

  • type ResultSetIO[A] = FreeC[ResultSetOp, A]

val next: ResultSetIO[Boolean] = liftFC(Next) def getInt(i: Int): ResultSetIO[Int] = liftFC(GetInt(a)) def getString(i: Int): ResultSetIO[String] = liftFC(GetString(a)) val close: ResultSetIO[Unit] = liftFC(Close)

slide-40
SLIDE 40

Wait, what?

import scalaz.{ Free, Coyoneda } import scalaz.Free.{ FreeC, liftFC }

  • type ResultSetIO[A] = FreeC[ResultSetOp, A]

ResultSetOp

val next: ResultSetIO[Boolean] = liftFC(Next) def getInt(i: Int): ResultSetIO[Int] = liftFC(GetInt(a)) def getString(i: Int): ResultSetIO[String] = liftFC(GetString(a)) val close: ResultSetIO[Unit] = liftFC(Close)

slide-41
SLIDE 41

Wait, what?

import scalaz.{ Free, Coyoneda } import scalaz.Free.{ FreeC, liftFC }

  • type ResultSetIO[A] = FreeC[ResultSetOp, A]

Smart Ctors ResultSetOp

val next: ResultSetIO[Boolean] = liftFC(Next) def getInt(i: Int): ResultSetIO[Int] = liftFC(GetInt(a)) def getString(i: Int): ResultSetIO[String] = liftFC(GetString(a)) val close: ResultSetIO[Unit] = liftFC(Close)

slide-42
SLIDE 42

Wait, what?

import scalaz.{ Free, Coyoneda } import scalaz.Free.{ FreeC, liftFC }

  • type ResultSetIO[A] = FreeC[ResultSetOp, A]

Smart Ctors Free Monad ResultSetOp

val next: ResultSetIO[Boolean] = liftFC(Next) def getInt(i: Int): ResultSetIO[Int] = liftFC(GetInt(a)) def getString(i: Int): ResultSetIO[String] = liftFC(GetString(a)) val close: ResultSetIO[Unit] = liftFC(Close)

slide-43
SLIDE 43

Programming

Now we can write programs in ResultSetIO using familiar monadic style.

case class Person(name: String, age: Int)

  • val getPerson: ResultSetIO[Person] =

for { name <- getString(1) age <- getInt(2) } yield Person(name, age)

slide-44
SLIDE 44

Programming

Now we can write programs in ResultSetIO using familiar monadic style.

case class Person(name: String, age: Int)

  • val getPerson: ResultSetIO[Person] =

for { name <- getString(1) age <- getInt(2) } yield Person(name, age)

Values!

slide-45
SLIDE 45

Programming

Now we can write programs in ResultSetIO using familiar monadic style.

case class Person(name: String, age: Int)

  • val getPerson: ResultSetIO[Person] =

for { name <- getString(1) age <- getInt(2) } yield Person(name, age)

Values! No ResultSet!

slide-46
SLIDE 46

Programming

Now we can write programs in ResultSetIO using familiar monadic style.

case class Person(name: String, age: Int)

  • val getPerson: ResultSetIO[Person] =

for { name <- getString(1) age <- getInt(2) } yield Person(name, age)

Values! No ResultSet! Composition!

slide-47
SLIDE 47

Functor Operations

A B C

f

D

f

E E

f

slide-48
SLIDE 48

Functor Operations

// Construct a program to read a Date at column n def getDate(n: Int): ResultSetIO[java.util.Date] = getLong(n).map(new java.util.Date(_))

A B C

f

D

f

E E

f

slide-49
SLIDE 49

Functor Operations

// Construct a program to read a Date at column n def getDate(n: Int): ResultSetIO[java.util.Date] = getLong(n).map(new java.util.Date(_))

A B C

f

D

f

E E

f

fpair strengthL strengthR fproduct as void

slide-50
SLIDE 50

Applicative Operations

A B C

f

D

f

E E

f

slide-51
SLIDE 51

Applicative Operations

A B C

f

D

f

E E

f

F

slide-52
SLIDE 52

Applicative Operations

A B C

f

D

f

E E

f

F

// Program to read a person val getPerson: ResultSetIO[Person] = (getString(1) |@| getInt(2)) { (s, n) => Person(s, n) }

slide-53
SLIDE 53

Applicative Operations

A B C

f

D

f

E E

f

F

// Program to move to the next row // and then read a person val getNextPerson: ResultSetIO[Person] = next *> getPerson

slide-54
SLIDE 54

Applicative Operations

A B C

f

D

f

E E

f

F

// Construct a program to read // a list of people def getPeople(n: Int): ResultSetIO[List[Person]] = getNextPerson.replicateM(n)

slide-55
SLIDE 55

Applicative Operations

// Implementation of replicateM def getPeople(n: Int): ResultSetIO[List[Person]] = getNextPerson.replicateM(n)

slide-56
SLIDE 56

Applicative Operations

// Implementation of replicateM def getPeople(n: Int): ResultSetIO[List[Person]] = getNextPerson.replicateM(n) // List[ResultSetIO[Person]] List.fill(n)(getNextPerson)

slide-57
SLIDE 57

Applicative Operations

// Implementation of replicateM def getPeople(n: Int): ResultSetIO[List[Person]] = getNextPerson.replicateM(n) // List[ResultSetIO[Person]] List.fill(n)(getNextPerson) // ResultSetIO[List[Person]] List.fill(n)(getNextPerson).sequence

slide-58
SLIDE 58

Applicative Operations

// Implementation of replicateM def getPeople(n: Int): ResultSetIO[List[Person]] = getNextPerson.replicateM(n) // List[ResultSetIO[Person]] List.fill(n)(getNextPerson) // ResultSetIO[List[Person]] List.fill(n)(getNextPerson).sequence

Awesome

slide-59
SLIDE 59

Applicative Operations

// Implementation of replicateM def getPeople(n: Int): ResultSetIO[List[Person]] = getNextPerson.replicateM(n) // List[ResultSetIO[Person]] List.fill(n)(getNextPerson) // ResultSetIO[List[Person]] List.fill(n)(getNextPerson).sequence

Awesome Traverse[List]

slide-60
SLIDE 60

Monad Operations

A B C

f

D

f

E E

f

F

slide-61
SLIDE 61

Monad Operations

// Now we can branch val getPersonOpt: ResultSetIO[Option[Person]] = next.flatMap { case true => getPerson.map(_.some) case false => none.point[ResultSetIO] }

A B C

f

D

f

E E

f

F

slide-62
SLIDE 62

Monad Operations

// And iterate! val getAllPeople: ResultSetIO[Vector[Person]] = getPerson.whileM[Vector](next)

A B C

f

D

f

E E

f

F

slide-63
SLIDE 63

Monad Operations

// And iterate! val getAllPeople: ResultSetIO[Vector[Person]] = getPerson.whileM[Vector](next)

Seriously

A B C

f

D

f

E E

f

F

slide-64
SLIDE 64

Okaaay...

slide-65
SLIDE 65

Interpreting

slide-66
SLIDE 66

Interpreting

  • To "run" our program we interpret it into some

target monad of our choice. We're returning our loaner in exchange for a "real" monad.

slide-67
SLIDE 67

Interpreting

  • To "run" our program we interpret it into some

target monad of our choice. We're returning our loaner in exchange for a "real" monad.

  • To do this, we need to provide a mapping from

ResultSetOp[A] (our original data type) to M[A] for any A.

slide-68
SLIDE 68

Interpreting

  • To "run" our program we interpret it into some

target monad of our choice. We're returning our loaner in exchange for a "real" monad.

  • To do this, we need to provide a mapping from

ResultSetOp[A] (our original data type) to M[A] for any A.

  • This is called a natural transformation and is

written ResultSetOp ~> M.

slide-69
SLIDE 69

Interpreting

def trans(rs: ResultSet) = new (ResultSetOp ~> IO) { def apply[A](fa: ResultSetOp[A]): IO[A] = fa match { case Next => IO(rs.next) case GetInt(i) => IO(rs.getInt(i)) case GetString(i) => IO(rs.getString(i)) case Close => IO(rs.close) // lots more } }

Here we interpret into scalaz.effect.IO

slide-70
SLIDE 70

Interpreting

def trans(rs: ResultSet) = new (ResultSetOp ~> IO) { def apply[A](fa: ResultSetOp[A]): IO[A] = fa match { case Next => IO(rs.next) case GetInt(i) => IO(rs.getInt(i)) case GetString(i) => IO(rs.getString(i)) case Close => IO(rs.close) // lots more } }

ResultSetOp

Here we interpret into scalaz.effect.IO

slide-71
SLIDE 71

Interpreting

def trans(rs: ResultSet) = new (ResultSetOp ~> IO) { def apply[A](fa: ResultSetOp[A]): IO[A] = fa match { case Next => IO(rs.next) case GetInt(i) => IO(rs.getInt(i)) case GetString(i) => IO(rs.getString(i)) case Close => IO(rs.close) // lots more } }

ResultSetOp Target Monad

Here we interpret into scalaz.effect.IO

slide-72
SLIDE 72

Running

def toIO[A](a: ResultSetIO[A], rs: ResultSet): IO[A] = Free.runFC(a)(trans(rs))

slide-73
SLIDE 73

Running

def toIO[A](a: ResultSetIO[A], rs: ResultSet): IO[A] = Free.runFC(a)(trans(rs))

Program written in FreeC

slide-74
SLIDE 74

Running

def toIO[A](a: ResultSetIO[A], rs: ResultSet): IO[A] = Free.runFC(a)(trans(rs))

Program written in FreeC Natural Transformation

slide-75
SLIDE 75

Running

def toIO[A](a: ResultSetIO[A], rs: ResultSet): IO[A] = Free.runFC(a)(trans(rs))

Program written in FreeC Target Natural Transformation

slide-76
SLIDE 76

Running

def toIO[A](a: ResultSetIO[A], rs: ResultSet): IO[A] = Free.runFC(a)(trans(rs))

Program written in FreeC Target Natural Transformation

val prog = getPerson.whileM[Vector](next) toIO(prog, rs).unsafePerformIO // Vector[Person]

slide-77
SLIDE 77
  • Fine. What's doobie?
slide-78
SLIDE 78
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.

slide-79
SLIDE 79
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A]

slide-80
SLIDE 80
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A]

slide-81
SLIDE 81
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A]

slide-82
SLIDE 82
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A] DriverManagerIO[A] NClobIO[A]

slide-83
SLIDE 83
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A] DriverManagerIO[A] NClobIO[A] PreparedStatementIO[A] RefIO[A]

slide-84
SLIDE 84
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A] DriverManagerIO[A] NClobIO[A] PreparedStatementIO[A] RefIO[A] ResultSetIO[A] SQLDataIO[A]

slide-85
SLIDE 85
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A] DriverManagerIO[A] NClobIO[A] PreparedStatementIO[A] RefIO[A] ResultSetIO[A] SQLDataIO[A] SQLInputIO[A] SQLOutputIO[A]

slide-86
SLIDE 86
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A] DriverManagerIO[A] NClobIO[A] PreparedStatementIO[A] RefIO[A] ResultSetIO[A] SQLDataIO[A] SQLInputIO[A] SQLOutputIO[A] StatementIO[A]

slide-87
SLIDE 87
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A] DriverManagerIO[A] NClobIO[A] PreparedStatementIO[A] RefIO[A] ResultSetIO[A] SQLDataIO[A] SQLInputIO[A] SQLOutputIO[A] StatementIO[A]

slide-88
SLIDE 88
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A] DriverManagerIO[A] NClobIO[A] PreparedStatementIO[A] RefIO[A] ResultSetIO[A] SQLDataIO[A] SQLInputIO[A] SQLOutputIO[A] StatementIO[A]

  • Pure functional support for all primitive operations.
slide-89
SLIDE 89
  • Fine. What's doobie?
  • Low-level API is basically exactly this, for all of JDBC.


BlobIO[A] CallableStatementIO[A] ClobIO[A] ConnectionIO[A] DatabaseMetaDataIO[A] DriverIO[A] DriverManagerIO[A] NClobIO[A] PreparedStatementIO[A] RefIO[A] ResultSetIO[A] SQLDataIO[A] SQLInputIO[A] SQLOutputIO[A] StatementIO[A]

  • Pure functional support for all primitive operations.
  • Machine-generated (!)
slide-90
SLIDE 90

Exception Handling

val ma = ConnectionIO[A]

  • ma.attempt // ConnectionIO[Throwable \/ A]

fail(wtf) // ConnectionIO[A]

  • // General // SQLException

ma.attemptSome(handler) ma.attemptSql ma.except(handler) ma.attemptSqlState ma.exceptSome(handler) ma.attemptSomeSqlState(handler) ma.onException(action) ma.exceptSql(handler) ma.ensuring(sequel) ma.exceptSqlState(handler) ma.exceptSomeSqlState(handler)

  • // PostgreSQL (hundreds more)

ma.onWarning(handler) ma.onDynamicResultSetsReturned(handler) ma.onImplicitZeroBitPadding(handler) ma.onNullValueEliminatedInSetFunction(handler) ma.onPrivilegeNotGranted(handler) ...

slide-91
SLIDE 91

Mapping via Typeclass

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • val ¡getPerson: ¡ResultSetIO[Person] ¡= ¡ ¡

for { name <- getString(1) age <- getInt(2) } yield Person(name, age)

slide-92
SLIDE 92

Mapping via Typeclass

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • val ¡getPerson: ¡ResultSetIO[Person] ¡= ¡ ¡

for { name <- get[String](1) age <- get[Int](2) } yield Person(name, age)

Abstract over return type

slide-93
SLIDE 93

Mapping via Typeclass

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • val ¡getPerson: ¡ResultSetIO[Person] ¡= ¡ ¡

for { p <- get[(String, Int)](1) } yield Person(p._1, p._2)

Generalize to tuples

slide-94
SLIDE 94

Mapping via Typeclass

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • val ¡getPerson: ¡ResultSetIO[Person] ¡= ¡ ¡

for { p <- get[Person](1) } yield p

Generalize to Products

slide-95
SLIDE 95

Mapping via Typeclass

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • val ¡getPerson: ¡ResultSetIO[Person] ¡= ¡ ¡

get[Person](1)

slide-96
SLIDE 96

Mapping via Typeclass

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • val ¡getPerson: ¡ResultSetIO[Person] ¡= ¡ ¡

get[Person]

slide-97
SLIDE 97

Mapping via Typeclass

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • get[Person]
slide-98
SLIDE 98

Mapping via Typeclass

case ¡class ¡Person(name: ¡String, ¡age: ¡Int) ¡

  • get[Person]

This is how you would really write it in doobie.

slide-99
SLIDE 99

Streaming

slide-100
SLIDE 100

Streaming

// One way to read into a List val readAll: ResultSetIO[List[Person]] = get[Person].whileM[List](next)

slide-101
SLIDE 101

Streaming

// One way to read into a List val readAll: ResultSetIO[List[Person]] = get[Person].whileM[List](next) // Another way val people: Process[ResultSetIO, Person] = process[Person]

slide-102
SLIDE 102

Streaming

// One way to read into a List val readAll: ResultSetIO[List[Person]] = get[Person].whileM[List](next) // Another way val people: Process[ResultSetIO, Person] = process[Person] people .filter(_.name.length > 5) .take(20) .moreStuff .list // ResultSetIO[List[Person]]

slide-103
SLIDE 103

High-Level API

slide-104
SLIDE 104

High-Level API

doobie programs can be nested, matching the natural structure of database interactions.

slide-105
SLIDE 105

High-Level API

doobie programs can be nested, matching the natural structure of database interactions. Some of the common patterns are provided in a high-level API that abstracts the lifting.

slide-106
SLIDE 106

High-Level API

def biggerThan(pop: Int) = sql""" select code, name, gnp from country where population > $pop """.query[Country] case class Country(name: String, code: String, pop: BigDecimal)

slide-107
SLIDE 107

High-Level API

def biggerThan(pop: Int) = sql""" select code, name, gnp from country where population > $pop """.query[Country]

Typed

case class Country(name: String, code: String, pop: BigDecimal)

slide-108
SLIDE 108

High-Level API

def biggerThan(pop: Int) = sql""" select code, name, gnp from country where population > $pop """.query[Country]

Typed Yields Countries

case class Country(name: String, code: String, pop: BigDecimal)

slide-109
SLIDE 109

High-Level API

scala> biggerThan(100000000) | .process // Process[ConnectionIO, Person] | .take(5) // Process[ConnectionIO, Person] | .list // ConnectionIO[List[Person]] | .transact(xa) // Task[List[Person]] | .run // List[Person] | .foreach(println) Country(BGD,Bangladesh,32852.00) Country(BRA,Brazil,776739.00) Country(IDN,Indonesia,84982.00) Country(IND,India,447114.00) Country(JPN,Japan,3787042.00)

slide-110
SLIDE 110

High-Level API

scala> biggerThan(100000000) | .process // Process[ConnectionIO, Person] | .take(5) // Process[ConnectionIO, Person] | .list // ConnectionIO[List[Person]] | .transact(xa) // Task[List[Person]] | .run // List[Person] | .foreach(println) Country(BGD,Bangladesh,32852.00) Country(BRA,Brazil,776739.00) Country(IDN,Indonesia,84982.00) Country(IND,India,447114.00) Country(JPN,Japan,3787042.00)

  • Transactor[Task]
slide-111
SLIDE 111

YOLO Mode

scala> biggerThan(100000000) | .process // Process[ConnectionIO, Person] | .take(5) // Process[ConnectionIO, Person] | .quick // Task[Unit] | .run // Unit Country(BGD,Bangladesh,32852.00) Country(BRA,Brazil,776739.00) Country(IDN,Indonesia,84982.00) Country(IND,India,447114.00) Country(JPN,Japan,3787042.00)

slide-112
SLIDE 112

YOLO Mode

scala> biggerThan(100000000) | .process // Process[ConnectionIO, Person] | .take(5) // Process[ConnectionIO, Person] | .quick // Task[Unit] | .run // Unit Country(BGD,Bangladesh,32852.00) Country(BRA,Brazil,776739.00) Country(IDN,Indonesia,84982.00) Country(IND,India,447114.00) Country(JPN,Japan,3787042.00)

  • Ambient Transactor

Just for Experimenting in the REPL

slide-113
SLIDE 113

YOLO MODE

scala> biggerThan(0).check.run

  • select code, name, gnp from country where population > ?
  • ✓ SQL Compiles and Typechecks

✓ P01 Int INTEGER (int4) ✓ C01 code CHAR (bpchar) NOT NULL String ✓ C02 name VARCHAR (varchar) NOT NULL String ✕ C03 gnp NUMERIC (numeric) NULL BigDecimal

  • Reading a NULL value into BigDecimal will result in a runtime
  • failure. Fix this by making the schema type NOT NULL or by

changing the Scala type to Option[BigDecimal]

slide-114
SLIDE 114

YOLO MODE

scala> biggerThan(0).check.run

  • select code, name, gnp from country where population > ?
  • ✓ SQL Compiles and Typechecks

✓ P01 Int INTEGER (int4) ✓ C01 code CHAR (bpchar) NOT NULL String ✓ C02 name VARCHAR (varchar) NOT NULL String ✕ C03 gnp NUMERIC (numeric) NULL BigDecimal

  • Reading a NULL value into BigDecimal will result in a runtime
  • failure. Fix this by making the schema type NOT NULL or by

changing the Scala type to Option[BigDecimal]

Can also do this in your unit tests!

slide-115
SLIDE 115

Much More

slide-116
SLIDE 116

Much More

  • Extremely simple custom type mappings for columns and

composite types.

slide-117
SLIDE 117

Much More

  • Extremely simple custom type mappings for columns and

composite types.

  • Connection Pooling with HikariCP, easily extended for
  • ther pooling implementations.
slide-118
SLIDE 118

Much More

  • Extremely simple custom type mappings for columns and

composite types.

  • Connection Pooling with HikariCP, easily extended for
  • ther pooling implementations.
  • Syntax aplenty, to make fancy types easier to work with.
slide-119
SLIDE 119

Much More

  • Extremely simple custom type mappings for columns and

composite types.

  • Connection Pooling with HikariCP, easily extended for
  • ther pooling implementations.
  • Syntax aplenty, to make fancy types easier to work with.
  • PostgreSQL Support: Geometric Types, Arrays, PostGIS

types, LISTEN/NOTIFY, CopyIn/Out, Large Objects, …

slide-120
SLIDE 120

Much More

  • Extremely simple custom type mappings for columns and

composite types.

  • Connection Pooling with HikariCP, easily extended for
  • ther pooling implementations.
  • Syntax aplenty, to make fancy types easier to work with.
  • PostgreSQL Support: Geometric Types, Arrays, PostGIS

types, LISTEN/NOTIFY, CopyIn/Out, Large Objects, …

  • … but works with any JDBC driver so people are using it

with H2, MySQL, MS-SQL, Hive, etc.

slide-121
SLIDE 121

Thanks!

https://github.com/tpolecat/doobie

gitter book of doobie

slide-122
SLIDE 122

Rows/ms (0.2.2)

1 10 100 1000 10 100 1000 10000 100000 1000000 list stream jdbc

slide-123
SLIDE 123

Rows/ms (0.2.3)

1 10 100 1000 10 100 1000 10000 100000 1000000 list stream jdbc

slide-124
SLIDE 124

Orientation

A growing family of Scala libraries for pure FP.

slide-125
SLIDE 125

Orientation

wartremover

A growing family of Scala libraries for pure FP.

slide-126
SLIDE 126

Orientation

monocle wartremover

A growing family of Scala libraries for pure FP.

slide-127
SLIDE 127

Orientation

monocle wartremover http4s

A growing family of Scala libraries for pure FP.

slide-128
SLIDE 128

Orientation

scalaz monocle wartremover http4s

A growing family of Scala libraries for pure FP.

slide-129
SLIDE 129

Orientation

scalaz monocle wartremover http4s remotely

A growing family of Scala libraries for pure FP.

slide-130
SLIDE 130

Orientation

scalaz scodec monocle wartremover http4s remotely

A growing family of Scala libraries for pure FP.

slide-131
SLIDE 131

Orientation

scalaz cats scodec monocle wartremover http4s remotely

A growing family of Scala libraries for pure FP.

slide-132
SLIDE 132

Orientation

scalaz cats scodec spire monocle wartremover http4s remotely

A growing family of Scala libraries for pure FP.

slide-133
SLIDE 133

Orientation

scalaz cats scodec spire monocle wartremover http4s remotely tut

A growing family of Scala libraries for pure FP.

slide-134
SLIDE 134

Orientation

scalaz cats scodec spire monocle wartremover http4s remotely tut

A growing family of Scala libraries for pure FP.

circe

slide-135
SLIDE 135

Orientation

scalaz cats argonaut scodec spire monocle wartremover http4s remotely tut

A growing family of Scala libraries for pure FP.

circe

slide-136
SLIDE 136

Orientation

scalaz cats argonaut scodec spire monocle wartremover http4s remotely atto tut

A growing family of Scala libraries for pure FP.

circe

slide-137
SLIDE 137

Orientation

scalaz cats argonaut scodec shapeless spire monocle wartremover http4s remotely atto tut

A growing family of Scala libraries for pure FP.

circe

slide-138
SLIDE 138

Orientation

scalaz cats argonaut scodec shapeless spire monocle wartremover http4s remotely doobie atto tut

A growing family of Scala libraries for pure FP.

circe

slide-139
SLIDE 139

Orientation

scalaz cats argonaut scodec shapeless spire monocle wartremover http4s remotely doobie atto tut

A growing family of Scala libraries for pure FP.

circe

… and many more