下準備としてパッケージオブジェクトあたりに以下のおまじないを書いておく1 この時点ですでに面倒だ!

import com.twitter.util.Future
import scalaz._
import Scalaz._

implicit def FutureFunctor: Functor[Future] = new Functor[Future] {
  def map[A, B](f: Future[A])(map: A => B): Future[B] = f.map(map(_))
}

implicit def FutureMonad: Monad[Future] = new Monad[Future] {
  def point[A](a: => A) = Future.value(a)
  def bind[A, B](f: Future[A])(fmap: A => Future[B]) = f.flatMap(fmap(_))
}

type ReaderTFuture[A, B] = ReaderT[Future, A, B]

object ReaderTFuture extends KleisliInstances with KleisliFunctions {
  def apply[A, B](f: A => Future[B]): ReaderTFuture[A, B] = kleisli(f)
}

Finch + MySQLでREST APIサーバを構築するで書いたUserがimplicit parameterを使ってClientを受け取っているので、これをReaderTに置き換えてみた。

import com.twitter.finagle.exp.mysql._

case class User(id: Long, email: String, screen_name: String)

object User {
  def find(id: Long): ReaderTFuture[Client, Option[User]] = ReaderTFuture { client =>
    client.prepare("SELECT * FROM users WHERE id = ?")(id) map { result =>
      result.asInstanceOf[ResultSet].rows.map(convertToEntity).headOption
    }
  }

  def create(email: String, screen_name: String): ReaderTFuture[Client, Long] = ReaderTFuture { client =>
    client.prepare("INSERT INTO users (email, screen_name) VALUES(?, ?)")(email, screen_name) map { result =>
      result.asInstanceOf[OK].insertId
    }
  }

  def convertToEntity(row: Row): User = {
    val LongValue(id)            = row("id").get
    val StringValue(email)       = row("email").get
    val StringValue(screen_name) = row("screen_name").get

    User(id, email, screen_name)
  }
}

こんな感じで使うことができる。

val client = Mysql.client
  .withCredentials("user", "password")
  .withDatabase("database")
  .newRichClient("127.0.0.1:3306")

(for {
  id   <- User.create("user1@example.com", "user1")
  user <- User.find(id)
} yield user match {
  case Some(v) => Created(v)
  case _ => NotFound(new Exception("xxxxx"))
}).run(client)

上記のように.runでまとめて渡すことも出来るしval user = User.find(id)(client)と直接渡すことも出来るので、使いやすいほうを選べば良さそう。

参考文献


  1. FutureをFunctorとMonadの型クラスのインスタンスにしておく必要がある。ReaderTではなくReader使う場合は不要。 ↩︎