Slick integration

It’s easy to embed a slick program in a Freestyle program using the freestyle-slick module. This module provides a very simple algebra with only one operation: run. Handlers for this algebra are available automatically when interpreting to scala.concurrent.Future or to any other target type for which an AsyncContext from the module freestyle-async is found implicitly. Freestyle already provides AsyncContext instances for fs2.Task in freestyle-async-fs2 and monix.eval.Task in freestyle-async-monix.

Here is an example that uses the Slick integration.

The standard freestyle and cats imports:

import freestyle._
import freestyle.implicits._

import cats.implicits._

And some imports for the freestyle-slick module and slick itself:

import freestyle.slick._

import _root_.slick.jdbc.JdbcBackend
import _root_.slick.jdbc.H2Profile.api._
import _root_.slick.jdbc.GetResult

We will ask the database for a Person. To keep this example concise, we just use a simple query and don’t bother to create actual tables:

case class Person(name: String, birthYear: Int)
// defined class Person

implicit val getPersonResult = GetResult(r => Person(r.nextString, r.nextInt))
// getPersonResult: slick.jdbc.GetResult[Person] = <function1>

val getPerson: DBIO[Person] = sql"SELECT 'Alonzo Church', 1903".as[Person].head
// getPerson: slick.jdbc.H2Profile.api.DBIO[Person] = slick.jdbc.StreamingInvokerAction$HeadAction@6d7bd051

We can embed this slick DBIO program in a Freestyle program. We start with the most trivial case by only using the SlickM algebra:

val slickFrees: FreeS[SlickM.Op, Person] =
  SlickM[SlickM.Op].run(getPerson)
// slickFrees: freestyle.FreeS[freestyle.slick.SlickM.Op,Person] = Free(...)

To execute this FreeS program, we need to import the freestyle-slick module implicits. In this Example, we are interpreting to scala.concurrent.Future and the following imports already include all the implicits needed to achieve that once an implicit Database is provided. For simplicity, we will use an H2 in memory database:

import freestyle.slick.implicits._
// import freestyle.slick.implicits._

import scala.concurrent.Future
// import scala.concurrent.Future

import scala.concurrent.duration._
// import scala.concurrent.duration._

import scala.concurrent.ExecutionContext.Implicits.global
// import scala.concurrent.ExecutionContext.Implicits.global

implicit val db = Database.forURL("jdbc:h2:mem:docs", driver = "org.h2.Driver")
// db: slick.jdbc.H2Profile.backend.DatabaseDef = slick.jdbc.JdbcBackend$DatabaseDef@4afbe343

val future = slickFrees.interpret[Future]
// future: scala.concurrent.Future[Person] = Future(<not completed>)

To check if we actually retrieve Alonzo Church as a Person, we can block the future in this example:

import scala.concurrent.Await
// import scala.concurrent.Await

Await.result(future, Duration.Inf)
// res2: Person = Person(Alonzo Church,1903)

SlickM in a module

Only using SlickM is not exactly useful in this case as it just adds an extra level of indirection on top of DBIO. As a more realistic example, we will use SlickM in a module together with another algebra:

@free trait Calc {
  def subtract(a: Int, b: Int): FS[Int]
}
// defined trait Calc
// defined object Calc

@module trait Example {
  val slickM: SlickM
  val calc: Calc
}
// defined trait Example
// defined object Example

A Handler[Future] for Calc:

implicit val calcHandler: Calc.Handler[Future] =
  new Calc.Handler[Future] {
    def subtract(a: Int, b: Int): Future[Int] = Future.successful(a - b)
  }
// calcHandler: Calc.Handler[scala.concurrent.Future] = $anon$1@76ac8b05

A program using the Example module:

def example[F[_]: SlickM](implicit example: Example[F]): FreeS[F, (Person, Int)] =
  for {
    person <- getPerson.liftFS[F]  // analogous to example.slickM.run(getPerson)
    age    <- example.calc.subtract(2017, person.birthYear)
  } yield (person, age)
// example: [F[_]](implicit evidence$1: freestyle.slick.SlickM[F], implicit example: Example[F])freestyle.FreeS[F,(Person, Int)]

We can use Example.Op as the functor and translate the resulting program to Future:

val result = example[Example.Op].interpret[Future]
// result: scala.concurrent.Future[(Person, Int)] = Future(<not completed>)

Await.result(result, Duration.Inf)
// res3: (Person, Int) = (Person(Alonzo Church,1903),114)