SlideShare a Scribd company logo
1 of 83
Download to read offline
Purely Functional Play Framework
Application
2018/03/17 ScalaMatsuri 2018
Naoki Aoyama
whoami
Naoki Aoyama
Twitter: @AoiroAoino
GitHub: @aoiroaoino
Working at
Septeni Original,Inc.
膨大なデータ収集から社内ワークフロー効率化までを行い、
広告主様の広告効果を高める広告運用最適化ツール
様々なメディアに対する広告運用やレポート、
クリエイティブ最適化を支援
Scalaを採用し、ドメイン駆動設計(DDD)で開発
グループ会社のコミックスマート社が手掛ける
連載型新作マンガ配信サービス
アプリは900万 DL を超え、オリジナルマンガも100作以上配信。
2017年12月にはアプリ内にアニメ視聴機能を実装し、
オリジナルアニメの配信を開始。
Scala + DDD ほか、 Android 版も Scala で開発。※ iOS 版は Swift
セプテーニ・オリジナルでは
いつでも Scala をやってみたい方を募集しております!
Scala や DDD 未経験でも問題ありません!
入社の研修カリキュラムを通して
Scala と DDD のトレーニングを積むことが出来ます。
</head>
<body>
Let’s get started!
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
Play Framework with Google Guice
- Play v2.4.0 より Google Guice を用いた DI の仕組みが取り
入れられた
- 大規模なアプリケーションには DI は欠かせない
- 実際、慣れれば便利。※慣れれば
Google Guice is introduce to Play Framework from
v2.4.0. It’s useful once you get used.
Sample Application on Play Framework
- Play Framework v2.6.12
- Scala 2.12.4
Sample Application on Play Framework
package controllers
import javax.inject._
import play.api.mvc._
import models.Greeting
@Singleton
class HomeController @Inject()(cc: ControllerComponents,
@Named("en") greeting: Greeting)
extends AbstractController(cc) {
def index() = Action { implicit request: Request[AnyContent] =>
Ok(views.html.index())
}
def message(name: String) = Action { Ok(greeting.message(name)) }
}
Sample Application on Play Framework
package models
trait Greeting {
def message(name: String): String
}
class EnglishGreeting extends Greeting {
override def message(name: String) = s"Hello, $name"
}
class JapaneseGreeting extends Greeting {
override def message(name: String) = s"こんにちは、$name"
}
Sample Application on Play Framework
import com.google.inject.AbstractModule
import com.google.inject.name.Names
import models._
class Module extends AbstractModule {
override def configure(): Unit = {
bind(classOf[Greeting])
.annotatedWith(Names.named("en"))
.to(classOf[EnglishGreeting])
bind(classOf[Greeting])
.annotatedWith(Names.named("jp"))
.to(classOf[JapaneseGreeting])
}
}
Play Framework with Google Guice
Google Guice is a runtime DI library
- 実行時に依存性を解決する
- Play で主に使われるのは Constructor Injection
Constructor Injection is mostly used in Play
Framework.
全て Google Guice でやる必要はない
そもそもプログラムとインタープリタの分離がしたい。
インターフェースと実装を分けたい、後で入れ替えたい。
そして以下のような性質を持っていると嬉しい
- Compile Time DI
- Composability
- Testability
We want to separate program & execution. Compile
time DI, composability and testability are desired.
全て Google Guice でやる必要はない
純粋関数型プログラミング言語由来のツールを使っておけば全て
解決するんでしょ?
- Reader Monad
- Free Monad
- etc.
I know! Purely functional programming solves all the
things.
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
Separation of program & execution - Reader Monad
trait UserRepository {
def store(user: User)(implicit session: DBSession): Try[Unit]
def resolveByName(name: String)(implicit session: DBSession): Try[Option[User]]
def resolveAllByNames(name: String*)
(implicit session: DBSession): Try[List[User]]
}
class UserRepositoryOnJDBC extends UserRepository {
def store(user: User)(implicit dbSession: DBSession) = ???
def resolveByName(name: String)(implicit dbSession: DBSession) = ???
def resolveAllByNames(name: String*)(implicit dbSession: DBSession) = ???
}
Separation of program & execution - Reader Monad
trait UserRepositoryModule {
def userRepository: UserRepository
}
trait MessageRepositoryModule {
def messageRepository: MessageRepository
}
Separation of program & execution - Reader Monad
type Prog[A] = Reader[UserRepositoryModule with MessageRepositoryModule, A]
def getReplyTargetUsers(messageId: Long): Prog[Try[List[User]]] =
Reader { module =>
for {
messageOpt <- module.messageRepository.resolveBy(messageId)
names = messageOpt.fold(List.empty[String])(_.replyTargets)
users <- module.userRepository.resolveAllByNames(names: _*)
} yield users
}
Separation of program & execution - Reader Monad
val module = new UserRepositoryModule with MessageRepositoryModule {
override def userRepository = new UserRepositoryOnJDBC()
override def messageRepository = new MessageRepositoryOnJDBC()
}
MessageAPI.getReplyTargetUsers(1).run(module)
Separation of program & execution - Reader Monad
- Reader Monad では依存が複数になると扱いにくい。
型が冗長になる
- 引数に一つしか取れないので、Context 組み立てと分解の手
間が発生する
It’s hard to handle with reader monad when it has
multiple dependencies.
Separation of program & execution - Free Monad
type UserRepositoryFree[A] = Free[UserRepositoryOp, A]
sealed trait UserRepositoryOp[A]
object UserRepositoryOp {
case class Store(user: User) extends UserRepositoryOp[Unit]
case class ResolveByName(name: String) extends UserRepositoryOp[Option[User]]
def store(user: User): UserRepositoryFree[Unit] =
Free.liftF[UserRepositoryOp, Unit](Store(user))
def resolveByName(name: String): UserRepositoryFree[Option[User]] =
Free.liftF[UserRepositoryOp, Option[User]](ResolveByName(name))
}
Separation of program & execution - Free Monad
object UserRepository {
def store(user: User): UserRepositoryFree[Unit] =
UserRepositoryOp.store(user)
def resolveByName(name: String): UserRepositoryFree[Option[User]] =
UserRepositoryOp.resolveByName(name)
}
Separation of program & execution - Free Monad
object UserRepositoryOnJDBCInterp {
def futureInterp(session: DBSession)
(implicit ec: ExecutionContext): UserRepositoryOp ~> Future =
new (UserRepositoryOp ~> Future) {
def apply[A](op: UserRepositoryOp[A]): Future[A] =
op match {
case UserRepositoryOp.Store(user) =>
Future(...)
case UserRepositoryOp.ResolveByName(name) =>
Future(...)
}
}
}
Separation of program & execution - Free Monad
val dbSession = new DBSession()
UserRepository.resolveByName("John Doe")
.foldMap(futureInterp(dbSession))
Separation of program & execution - Free Monad
Free Monad は
- ボイラープレートが多い
でもコード自動生成に頼るのは微妙
- 実装時に何を処理の最小単位 (≒ 1つの ADT)とするか見極
めが難しい
- 異なる ADT の合成には一苦労
Free Monad requires too many boilerplates,
and composing multiple ADTs is difficult.
_人人人人人人人人人人_
> Tagless final style <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
What’s Tagless Final style
言語内 DSL (Embedded DSL) を作成する手法の一つ
Free Monad と比較して少ない記述量でプログラムと実装の分
離、DSL の合成が実現できる。
One method of creating EDSL. Tagless final has less
boilerplate.
Introduction to Tagless Final
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
Introduction to Tagless Final
object UserRepository {
implicit val tryUserRepository: UserRepository[Try] =
new UserRepository[Try] {
def store(user: User): Try[Unit] = ???
def resolveByName(name: String): Try[Option[User]] = ???
}
}
Introduction to Tagless Final
def prog[F[_]](userName: String)
(implicit userRepo: UserRepository[F]): F[Option[User]] =
userRepo.resolveByName(userName)
prog[Try]("John")
// scala.util.Try[Option[User]] = Success(None)
また宇宙からやってきた難しい概念なんでしょう...?
Where are you from?
Isn’t it purely functional Alien?
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
よくある UserRepository インターフェースと実装例
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
// impl
class UserRepositoryOnJDBC()(implicit ec: ExecutionContext)
extends UserRepository[DBSession] {
def store(user: User)(implicit ctx: DBSession): Future[Unit] =
???
def resolveByName(name: String)(implicit ctx: DBSession): Future[Option[User]] =
???
}
Problem 1:
各メソッドが Context を暗黙の値として受け取る
- Context を一つだけとることを前提としている
- 暗黙的な値が複数必要だった場合にはそれらをまとめるコン
テナを用意する必要がある
- メソッド内では受け取った値から自分に必要なものを取り出す
作業が発生する
Each method takes a context as an implicit
parameter.
Problem 1:
各メソッドが Context を暗黙の値として受け取る
class UserRepositoryCtx(ec: ExecutionContext, dbSession: DBSession)
// impl on database
class UserRepositoryOnJDBC() extends UserRepository[UserRepositoryCtx] {
def store(user: User)(implicit ctx: UserRepositoryCtx): Future[Unit] = {
implicit val (ec, s) = (ctx.ec, ctx.dbSession)
??? // update
}
def resolveByName(name: String)
(implicit ctx: UserRepositoryCtx): Future[Option[User]] = {
implicit val (ec, s) = (ctx.ec, ctx.dbSession)
??? // select
}
}
Problem 1:
各メソッドが Context を暗黙の値として受け取る
case class DummyCtx()
// impl by memory
object UserRepositoryOnMemory extends UserRepository[DummyCtx]{
private val db = mutable.Map.empty[UserId, User]
def store(user: User)(implicit ctx: DummyCtx): Future[Unit] =
Future.successful(db.update(user.id, user))
def resolveByName(name: String)(implicit ctx: DummyCtx): Future[Option[User]] =
Future.successful(db.collectFirst {
case (_, u@User(_, n, _)) if n == name => u
})
}
Problem 2:
メソッドの実行結果をどう表現するか
いくつかのパターンが考えられる
- 呼び出し側に責任を押し付け失敗を全て例外で throw
- 例外を型で表現するために Try に包む
- 非同期処理もありうるから Future にしておく
- etc.
How to express the return value as a type.
Problem 2:
メソッドの実行結果をどう表現するか
// It’s simply! but throwable...
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Unit
def resolveByName(name: String)(implicit ctx: Ctx): Option[User]
}
// It’s not throwable! but blocking only...
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Try[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Try[Option[User]]
}
// It’s non blocking! but needs ExecutionContext in Ctx...
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
つまり、どうすればいいのか
UserRepository インターフェース内に実装側の都合を一切持ち
込まないというのが重要
- Context という概念を UserRepository 内に持ち込まない
- Context を暗黙の値にとるような前提の実装をやめる
- メソッドの実行方法や失敗の表現など、まるっと含めて返り値
の型を抽象的に表現する
Do not bring implementation related things to
UserRepository interface.
Remove implicit parameter
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
Remove implicit parameter
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
// 1. remove implicit
trait UserRepository[Ctx] {
def store(user: User)(ctx: Ctx): Future[Unit]
def resolveByName(name: String)(ctx: Ctx): Future[Option[User]]
}
Remove implicit parameter
trait UserRepository[Ctx] {
def store(user: User)(implicit ctx: Ctx): Future[Unit]
def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]]
}
// 1. remove implicit
trait UserRepository[Ctx] {
def store(user: User)(ctx: Ctx): Future[Unit]
def resolveByName(name: String)(ctx: Ctx): Future[Option[User]]
}
// 2. modify return function
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
Remove implicit parameter
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
// easy to use implicit parameter within impl.
class UserRepositoryOnJDBC(implicit ec: ExecutionContext)
extends UserRepository[DBSession] {
def store(user: User): DBSession => Future[Unit] = {
implicit dbSession =>
???
}
def resolveByName(name: String): DBSession => Future[Option[User]] = {
implicit dbSession =>
???
}
}
Really need Context?
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
Really need Context?
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
// 1. create type alias named `Exec` and replace return type
trait UserRepository[Ctx] {
type Exec[R] = Ctx => Future[R]
def store(user: User): Exec[Unit]
def resolveByName(name: String): Exec[Option[User]]
}
Really need Context?
trait UserRepository[Ctx] {
def store(user: User): Ctx => Future[Unit]
def resolveByName(name: String): Ctx => Future[Option[User]]
}
// 1. create type alias named `Exec` and replace return type
trait UserRepository[Ctx] {
type Exec[R] = Ctx => Future[R]
def store(user: User): Exec[Unit]
def resolveByName(name: String): Exec[Option[User]]
}
// 2. add `F[_]` type parameter and remove `Exec`
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
It’s more abstract interface
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
type Query[A] = Reader[DBSession, A]
object UserRepositoryOnJDBC {
implicit def queryUserRepository: UserRepository[Query] =
new UserRepository[Query] {
def store(user: User): Query[Unit] =
Reader { implicit dbSession => ??? }
def resolveByName(name: String): Query[Option[User]] =
Reader { implicit dbSession => ??? }
}
}
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
type AsyncQuery[A] = ReaderT[Task, DBSession, A]
object UserRepositoryOnJDBC {
implicit def asyncQueryUserRepository: UserRepository[AsyncQuery] =
new UserRepository[AsyncQuery] {
def store(user: User): AsyncQuery[Unit] =
ReaderT { implicit dbSession => Task(???) }
def resolveByName(name: String): AsyncQuery[Option[User]] =
ReaderT { implicit dbSession => Task(???) }
}
}
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
type UserRepositoryFree[A] = Free[UserRepositoryOp, A]
object UserRepositoryOnJDBC {
implicit def freeUserRepository: UserRepository[UserRepositoryFree] =
new UserRepository[UserRepositoryFree] {
def store(user: User): UserRepositoryFree[Unit] =
UserRepositoryOp.store(user)
def resolveByName(name: String): UserRepositoryFree[Option[User]] =
UserRepositoryOp.resolveByName(name)
}
}
UserRepository implementation
trait UserRepository[F[_]] {
def store(user: User): F[Unit]
def resolveByName(name: String): F[Option[User]]
}
type ConnectionIO[A] = Free[ConnectionOp, A]
object UserRepositoryOnJDBC {
implicit def doobieUserRepository: UserRepository[ConnectionIO] =
new UserRepository[ConnectionIO] {
def store(user: User): ConnectionIO[Unit] =
sql"...".update.run.map(_ => ())
def resolveByName(name: String): ConnectionIO[Option[User]] =
sql"...".query[User].option
}
}
UserRepository implementation
object UserRepository {
def apply[F[_]](implicit F: UserRepository[F]): UserRepository[F] = F
}
val dbSession = new DBSession()
type Query[A] = Reader[DBSession, A]
UserRepository[Query].resolveByName("John Doe")
.run(dbSession)
type AsyncQuery[A] = ReaderT[Task, DBSession, A]
UserRepository[AsyncQuery].resolveByName("John Doe")
.run(dbSession).runAsync
type UserRepositoryFree[A] = Free[UserRepositoryOp, A]
UserRepository[UserRepositoryFree].resolveByName("John Doe")
.foldMap(futureInterp(dbSession))
UserRepository implementation
object UserRepository {
def apply[F[_]](implicit F: UserRepository[F]): UserRepository[F] = F
}
val xa = Transactor.fromDriverManager[Task](...) // for doobie
type ConnectionIO[A] = Free[ConnectionOp, A]
UserRepository[ConnectionIO].resolveByName("John Doe")
.transact(xa).runAsync
UserController implementation
object UserController {
def getUser[F[_]: Monad: UserRepository](id: Long): F[Result] =
UserRepository[F].resolveBy(id).map {
case Some(user) => Results.Ok(GetUserResponse.from(user).toJson)
case None => Results.NotFound
}
}
UserController implementation
class UserController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
import UserRepositoryOnJDBC._ // impls
def getUser(id: Long) = Action {
UserController.getUser[Query](id).run(dbSession)
}
def getUserAsync(id: Long) = Action.async {
UserController.getUser[AsyncQuery](id).run(dbSession).runAsync
}
}
UserController testing
class UserRepositoryMock extends UserRepository[Id] {
def store(user: User): Id[Unit] = ???
def resolveBy(id: Long): Id[Option[User]] = ???
def resolveByName(name: String): Id[Option[User]] = ???
}
UserController testing
class UserControllerSpec extends PlaySpec {
"UserController GET" should {
"get user from static controller" in {
implicit val mock = new UserRepositoryMock {
override def resolveBy(id: Long): Option[User] =
if (id == 100)
Some(User(100, "John"))
else
None
}
UserController.getUser[Id](100).header.status mustBe OK
UserController.getUser[Id](999).header.status mustBe NOT_FOUND
}
}
}
UserController testing
object UserController {
// ...
def storeAndGet[F[_]: Monad: UserRepository](name: String): F[Result] =
for {
_ <- UserRepository[F].store(User(Random.nextLong(), name))
user <- UserRepository[F].resolveByName(name)
} yield {
user match {
case Some(u) => Results.Ok(GetUserResponse.from(u).toJson)
case None => Results.InternalServerError
}
}
}
UserController testing
type DB = List[User]
class UserRepositoryStateMock extends UserRepository[State[DB, ?]] {
def store(user: User): State[DB, Unit] = ???
def resolveBy(id: Long): State[DB, Option[User]] = ???
def resolveByName(name: String): State[DB, Option[User]] = ???
}
UserController testing
class UserControllerSpec extends PlaySpec {
"UserController GET" should {
"store and get user from static controller" in {
implicit val mock = new UserRepositoryStateMock {
override def store(user: User): State[DB, Unit] =
State(db => (user :: db, ()))
override def resolveByName(name: String): State[DB, Option[User]] =
State.inspect(_.find(_.name == name))
}
UserController.storeAndGet[State[DB, ?]]("John")
.runEmptyA.value.header.status mustBe OK
}
}
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
F[_] が異なる場合は for 式で合成できない
object MessageController {
def getUserMessages[F[_]: Monad: MessageRepository: UserRepository](
userId: Long
): F[Result] =
for {
user <- UserRepository[F].resolveBy(userId)
messages <- MessageRepository[F].resolveAllByUserId(userId)
} yield {
user match {
case Some(u) => Results.Ok(GetMessagesResponse.from(u, messages).toJson)
case None => Results.NotFound
}
}
}
F[_] が異なる場合は for 式で合成できない
type AsyncQuery[A] = ReaderT[Task, DBSession, A]
type AsyncCommand[A] = ReaderT[Task, RedisConnection, A]
object MessageRepositoryOnJDBC {
// MessageRepository[AsyncQuery] is not implemented
implicit def redisConnectionMessageRepository: MessageRepository[AsyncCommand] =
new MessageRepository[AsyncCommand] {
def resolveBy(id: Long): AsyncCommand[Option[Message]] = ???
def store(message: Message): AsyncCommand[Unit] = ???
def resolveAllByUserId(userId: Long): AsyncCommand[List[Message]] = ???
}
}
F[_] が異なる場合は for 式で合成できない
[pfpfap] $ compile
[info] Compiling 1 Scala source to /Users/aoiroaoino/git/pfpf/bbs/target/scala-2.12/classes …
...
[error] /Users/aoiroaoino/git/pfpf/bbs/app/controllers/MessageController.scala:26:50:
could not find implicit value for evidence parameter of type
models.repository.MessageRepository[models.AsyncQuery]
[error] MessageController.getUserMessages[AsyncQuery](userId).run(dbSession).runAsync
[error] ^
F[_] が異なる場合は for 式で合成できない
いくつかの解決策がある
- Monad を積み上げる
- F[_] を具体的な型にしてから controller 合成する
- 自然変換を使って型を揃える
There are some solutions. Stacking Monads,
composing concrete types, Natural Transformation.
Stacking Monads
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type SpecialAction[A] = ReaderT[ReaderT[Task, DBSession, ?], RedisConnection, A]
def getUserMessagesSM(userId: Long) = Action.async {
MessageController.getUserMessages[SpecialAction](userId)
.run(redisConnection) // RedisConnection => ReaderT[Task, DBSession, A]
.run(dbSession) // DBSession => Task[A]
.runAsync // CancelableFuture[A]
}
}
Composing concrete types
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
def getUserMessages(userId: Long) = Action.async {
(for {
user <- UserRepository[AsyncQuery].resolveBy(userId).run(dbSession)
messages <- MessageRepository[AsyncCommand].resolveAllByUserId(userId)
.run(redisConnection)
} yield {
user match {
case Some(u) => Results.Ok(GetMessagesResponse.from(u, messages).toJson)
case None => Results.NotFound
}
}).runAsync
}
}
Use natural transformation and mapK()
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type AsyncQuery[A] = ReaderT[Task, DBSession, A]
type AsyncCommand[A] = ReaderT[Task, RedisConnection, A]
// common type of AsyncQuery and AsyncCommand
type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A]
def getUserMessages(userId: Long) = Action.async {
MessageController.getUserMessages[Exec](userId)
.run((dbSession, redisConnection)).runAsync
}
}
Use natural transformation and mapK()
trait MessageRepository[F[_]] {
def store(message: Message): F[Unit]
def resolveBy(id: Long): F[Option[Message]]
def resolveAllByUserId(userId: Long): F[List[Message]]
def mapK[G[_]](fk: F ~> G): MessageRepository[G] =
new MessageRepository[G] {
def store(message: Message): G[Unit] =
fk(self.store(message))
def resolveBy(id: Long): G[Option[Message]] =
fk(self.resolveBy(id))
def resolveAllByUserId(userId: Long): G[List[Message]] =
fk(self.resolveAllByUserId(userId))
}
}
Use natural transformation and mapK()
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A]
val asyncQueryToExec: AsyncQuery ~> Exec =
new (AsyncQuery ~> Exec) {
def apply[A](aq: AsyncQuery[A]): Exec[A] =
ReaderT { case (dbSession, _) => aq.run(dbSessioin) }
}
val asyncCommandToExec: AsyncCommand ~> Exec =
new (AsyncCommand ~> Exec) {
def apply[A](ac: AsyncCommand[A]): Exec[A] =
ReaderT { case (_, redisConn) => ac.run(redisConn) }
}
}
Use natural transformation and mapK()
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A]
val asyncQueryToExec: AsyncQuery ~> Exec = ...
val asyncCommandToExec: AsyncCommand ~> Exec = ...
implicit val execUserRepository: UserRepository[Exec] =
UserRepository[AsyncQuery].mapK(asyncQueryToExec)
implicit val execMessageRepository: MessageRepository[Exec] =
MessageRepository[AsyncCommand].mapK(asyncCommandToExec)
}
Use natural transformation and mapK()
class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext)
extends AbstractController(cc) {
// ...
type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A]
val asyncQueryToExec: AsyncQuery ~> Exec = ...
val asyncCommandToExec: AsyncCommand ~> Exec = ...
implicit val execUserRepository: UserRepository[Exec] = ...
implicit val execMessageRepository: MessageRepository[Exec] = ...
def getUserMessages(userId: Long) = Action.async {
MessageController.getUserMessages[Exec](userId)
.run((dbSession, redisConnection)).runAsync
}
}
Agenda
Introduction
- Play Framework with Google Guice
- Purely functional separation of program & execution
What’s Tagless Final style?
- Overview
- It’s your neighbor
Tagless Final style practice in Play Framework
- Purely, so useful!
- But, No Silver Bullet
Conclusion
Conclusion
Google Guice でアプリを全て組み立てる必要はない
- プログラムとインタープリタの分離をする方法はいくつもあ
る。
- Reader や Free もいいけど直接使うと不便さもある。
There are many ways to separate program &
interpreter.
Conclusion
Tagless Final 便利
- 手で書いても苦にならない程度のコード量
- 複数の DSL を比較的簡単に合成できる
- F[_] が異なる場合に一工夫必要。銀の弾丸ではない。
- Play Framework に限らずいろんな場面で使える!
Tagless final is useful because it has less boilerplate.

More Related Content

What's hot

FP 201 - Unit4 Part 2
FP 201 - Unit4 Part 2FP 201 - Unit4 Part 2
FP 201 - Unit4 Part 2
rohassanie
 
C++11 smart pointer
C++11 smart pointerC++11 smart pointer
C++11 smart pointer
Lei Yu
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
David Padbury
 

What's hot (20)

Introduction to Objective - C
Introduction to Objective - CIntroduction to Objective - C
Introduction to Objective - C
 
Core concepts-javascript
Core concepts-javascriptCore concepts-javascript
Core concepts-javascript
 
The Future of C++
The Future of C++The Future of C++
The Future of C++
 
Uncommon Design Patterns
Uncommon Design PatternsUncommon Design Patterns
Uncommon Design Patterns
 
FP 201 - Unit4 Part 2
FP 201 - Unit4 Part 2FP 201 - Unit4 Part 2
FP 201 - Unit4 Part 2
 
Smart pointers
Smart pointersSmart pointers
Smart pointers
 
C++11 smart pointer
C++11 smart pointerC++11 smart pointer
C++11 smart pointer
 
Lecture 3, c++(complete reference,herbet sheidt)chapter-13
Lecture 3, c++(complete reference,herbet sheidt)chapter-13Lecture 3, c++(complete reference,herbet sheidt)chapter-13
Lecture 3, c++(complete reference,herbet sheidt)chapter-13
 
STL ALGORITHMS
STL ALGORITHMSSTL ALGORITHMS
STL ALGORITHMS
 
Fancy talk
Fancy talkFancy talk
Fancy talk
 
Let's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java APILet's make a contract: the art of designing a Java API
Let's make a contract: the art of designing a Java API
 
C++11 Idioms @ Silicon Valley Code Camp 2012
C++11 Idioms @ Silicon Valley Code Camp 2012 C++11 Idioms @ Silicon Valley Code Camp 2012
C++11 Idioms @ Silicon Valley Code Camp 2012
 
Objective-C Crash Course for Web Developers
Objective-C Crash Course for Web DevelopersObjective-C Crash Course for Web Developers
Objective-C Crash Course for Web Developers
 
Testing for share
Testing for share Testing for share
Testing for share
 
JavaScript Growing Up
JavaScript Growing UpJavaScript Growing Up
JavaScript Growing Up
 
What's New in C++ 11?
What's New in C++ 11?What's New in C++ 11?
What's New in C++ 11?
 
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...
[C++] The Curiously Recurring Template Pattern: Static Polymorphsim and Expre...
 
Advanced Python, Part 2
Advanced Python, Part 2Advanced Python, Part 2
Advanced Python, Part 2
 
C++11
C++11C++11
C++11
 
Introduction to ad-3.4, an automatic differentiation library in Haskell
Introduction to ad-3.4, an automatic differentiation library in HaskellIntroduction to ad-3.4, an automatic differentiation library in Haskell
Introduction to ad-3.4, an automatic differentiation library in Haskell
 

Similar to purely_functional_play_framework_application

WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
Fabio Franzini
 
Introduction to Gradio library in python.pptx
Introduction to Gradio library in python.pptxIntroduction to Gradio library in python.pptx
Introduction to Gradio library in python.pptx
vahid67ebrahimian
 

Similar to purely_functional_play_framework_application (20)

ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...
ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...
ClojureScript - Making Front-End development Fun again - John Stevenson - Cod...
 
PhpStorm: Symfony2 Plugin
PhpStorm: Symfony2 PluginPhpStorm: Symfony2 Plugin
PhpStorm: Symfony2 Plugin
 
Introduction to Software Development
Introduction to Software DevelopmentIntroduction to Software Development
Introduction to Software Development
 
Reactive datastore demo (2020 03-21)
Reactive datastore demo (2020 03-21)Reactive datastore demo (2020 03-21)
Reactive datastore demo (2020 03-21)
 
Django Framework Overview forNon-Python Developers
Django Framework Overview forNon-Python DevelopersDjango Framework Overview forNon-Python Developers
Django Framework Overview forNon-Python Developers
 
Griffon: Re-imaging Desktop Java Technology
Griffon: Re-imaging Desktop Java TechnologyGriffon: Re-imaging Desktop Java Technology
Griffon: Re-imaging Desktop Java Technology
 
OSGi DevCon 2009 Review
OSGi DevCon 2009 ReviewOSGi DevCon 2009 Review
OSGi DevCon 2009 Review
 
MobileConf 2021 Slides: Let's build macOS CLI Utilities using Swift
MobileConf 2021 Slides:  Let's build macOS CLI Utilities using SwiftMobileConf 2021 Slides:  Let's build macOS CLI Utilities using Swift
MobileConf 2021 Slides: Let's build macOS CLI Utilities using Swift
 
Flutter vs Java Graphical User Interface Frameworks - text
Flutter vs Java Graphical User Interface Frameworks - textFlutter vs Java Graphical User Interface Frameworks - text
Flutter vs Java Graphical User Interface Frameworks - text
 
Drupal 8 preview_slideshow
Drupal 8 preview_slideshowDrupal 8 preview_slideshow
Drupal 8 preview_slideshow
 
Going open source with small teams
Going open source with small teamsGoing open source with small teams
Going open source with small teams
 
Griffon Presentation
Griffon PresentationGriffon Presentation
Griffon Presentation
 
ANDROID FDP PPT
ANDROID FDP PPTANDROID FDP PPT
ANDROID FDP PPT
 
Advantages of golang development services &amp; 10 most used go frameworks
Advantages of golang development services &amp; 10 most used go frameworksAdvantages of golang development services &amp; 10 most used go frameworks
Advantages of golang development services &amp; 10 most used go frameworks
 
Exploring Google (Cloud) APIs with Python & JavaScript
Exploring Google (Cloud) APIs with Python & JavaScriptExploring Google (Cloud) APIs with Python & JavaScript
Exploring Google (Cloud) APIs with Python & JavaScript
 
WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...WebNet Conference 2012 - Designing complex applications using html5 and knock...
WebNet Conference 2012 - Designing complex applications using html5 and knock...
 
Introduction to Gradio library in python.pptx
Introduction to Gradio library in python.pptxIntroduction to Gradio library in python.pptx
Introduction to Gradio library in python.pptx
 
Shuzworld Analysis
Shuzworld AnalysisShuzworld Analysis
Shuzworld Analysis
 
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud Run
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud RunDesigning flexible apps deployable to App Engine, Cloud Functions, or Cloud Run
Designing flexible apps deployable to App Engine, Cloud Functions, or Cloud Run
 
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)
UX Prototyping: New Principles and Tools (HTP Grodno, 20.03.2018)
 

Recently uploaded

TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
mohitmore19
 
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
anilsa9823
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
Health
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 

Recently uploaded (20)

The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female serviceCALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
CALL ON ➥8923113531 🔝Call Girls Badshah Nagar Lucknow best Female service
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 

purely_functional_play_framework_application

  • 1. Purely Functional Play Framework Application 2018/03/17 ScalaMatsuri 2018 Naoki Aoyama
  • 2. whoami Naoki Aoyama Twitter: @AoiroAoino GitHub: @aoiroaoino Working at Septeni Original,Inc.
  • 3.
  • 6. セプテーニ・オリジナルでは いつでも Scala をやってみたい方を募集しております! Scala や DDD 未経験でも問題ありません! 入社の研修カリキュラムを通して Scala と DDD のトレーニングを積むことが出来ます。
  • 8. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 9. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 10. Play Framework with Google Guice - Play v2.4.0 より Google Guice を用いた DI の仕組みが取り 入れられた - 大規模なアプリケーションには DI は欠かせない - 実際、慣れれば便利。※慣れれば Google Guice is introduce to Play Framework from v2.4.0. It’s useful once you get used.
  • 11. Sample Application on Play Framework - Play Framework v2.6.12 - Scala 2.12.4
  • 12. Sample Application on Play Framework package controllers import javax.inject._ import play.api.mvc._ import models.Greeting @Singleton class HomeController @Inject()(cc: ControllerComponents, @Named("en") greeting: Greeting) extends AbstractController(cc) { def index() = Action { implicit request: Request[AnyContent] => Ok(views.html.index()) } def message(name: String) = Action { Ok(greeting.message(name)) } }
  • 13. Sample Application on Play Framework package models trait Greeting { def message(name: String): String } class EnglishGreeting extends Greeting { override def message(name: String) = s"Hello, $name" } class JapaneseGreeting extends Greeting { override def message(name: String) = s"こんにちは、$name" }
  • 14. Sample Application on Play Framework import com.google.inject.AbstractModule import com.google.inject.name.Names import models._ class Module extends AbstractModule { override def configure(): Unit = { bind(classOf[Greeting]) .annotatedWith(Names.named("en")) .to(classOf[EnglishGreeting]) bind(classOf[Greeting]) .annotatedWith(Names.named("jp")) .to(classOf[JapaneseGreeting]) } }
  • 15.
  • 16. Play Framework with Google Guice Google Guice is a runtime DI library - 実行時に依存性を解決する - Play で主に使われるのは Constructor Injection Constructor Injection is mostly used in Play Framework.
  • 17.
  • 18. 全て Google Guice でやる必要はない そもそもプログラムとインタープリタの分離がしたい。 インターフェースと実装を分けたい、後で入れ替えたい。 そして以下のような性質を持っていると嬉しい - Compile Time DI - Composability - Testability We want to separate program & execution. Compile time DI, composability and testability are desired.
  • 19. 全て Google Guice でやる必要はない 純粋関数型プログラミング言語由来のツールを使っておけば全て 解決するんでしょ? - Reader Monad - Free Monad - etc. I know! Purely functional programming solves all the things.
  • 20. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 21. Separation of program & execution - Reader Monad trait UserRepository { def store(user: User)(implicit session: DBSession): Try[Unit] def resolveByName(name: String)(implicit session: DBSession): Try[Option[User]] def resolveAllByNames(name: String*) (implicit session: DBSession): Try[List[User]] } class UserRepositoryOnJDBC extends UserRepository { def store(user: User)(implicit dbSession: DBSession) = ??? def resolveByName(name: String)(implicit dbSession: DBSession) = ??? def resolveAllByNames(name: String*)(implicit dbSession: DBSession) = ??? }
  • 22. Separation of program & execution - Reader Monad trait UserRepositoryModule { def userRepository: UserRepository } trait MessageRepositoryModule { def messageRepository: MessageRepository }
  • 23. Separation of program & execution - Reader Monad type Prog[A] = Reader[UserRepositoryModule with MessageRepositoryModule, A] def getReplyTargetUsers(messageId: Long): Prog[Try[List[User]]] = Reader { module => for { messageOpt <- module.messageRepository.resolveBy(messageId) names = messageOpt.fold(List.empty[String])(_.replyTargets) users <- module.userRepository.resolveAllByNames(names: _*) } yield users }
  • 24. Separation of program & execution - Reader Monad val module = new UserRepositoryModule with MessageRepositoryModule { override def userRepository = new UserRepositoryOnJDBC() override def messageRepository = new MessageRepositoryOnJDBC() } MessageAPI.getReplyTargetUsers(1).run(module)
  • 25. Separation of program & execution - Reader Monad - Reader Monad では依存が複数になると扱いにくい。 型が冗長になる - 引数に一つしか取れないので、Context 組み立てと分解の手 間が発生する It’s hard to handle with reader monad when it has multiple dependencies.
  • 26. Separation of program & execution - Free Monad type UserRepositoryFree[A] = Free[UserRepositoryOp, A] sealed trait UserRepositoryOp[A] object UserRepositoryOp { case class Store(user: User) extends UserRepositoryOp[Unit] case class ResolveByName(name: String) extends UserRepositoryOp[Option[User]] def store(user: User): UserRepositoryFree[Unit] = Free.liftF[UserRepositoryOp, Unit](Store(user)) def resolveByName(name: String): UserRepositoryFree[Option[User]] = Free.liftF[UserRepositoryOp, Option[User]](ResolveByName(name)) }
  • 27. Separation of program & execution - Free Monad object UserRepository { def store(user: User): UserRepositoryFree[Unit] = UserRepositoryOp.store(user) def resolveByName(name: String): UserRepositoryFree[Option[User]] = UserRepositoryOp.resolveByName(name) }
  • 28. Separation of program & execution - Free Monad object UserRepositoryOnJDBCInterp { def futureInterp(session: DBSession) (implicit ec: ExecutionContext): UserRepositoryOp ~> Future = new (UserRepositoryOp ~> Future) { def apply[A](op: UserRepositoryOp[A]): Future[A] = op match { case UserRepositoryOp.Store(user) => Future(...) case UserRepositoryOp.ResolveByName(name) => Future(...) } } }
  • 29. Separation of program & execution - Free Monad val dbSession = new DBSession() UserRepository.resolveByName("John Doe") .foldMap(futureInterp(dbSession))
  • 30. Separation of program & execution - Free Monad Free Monad は - ボイラープレートが多い でもコード自動生成に頼るのは微妙 - 実装時に何を処理の最小単位 (≒ 1つの ADT)とするか見極 めが難しい - 異なる ADT の合成には一苦労 Free Monad requires too many boilerplates, and composing multiple ADTs is difficult.
  • 32. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 33. What’s Tagless Final style 言語内 DSL (Embedded DSL) を作成する手法の一つ Free Monad と比較して少ない記述量でプログラムと実装の分 離、DSL の合成が実現できる。 One method of creating EDSL. Tagless final has less boilerplate.
  • 34. Introduction to Tagless Final trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] }
  • 35. Introduction to Tagless Final object UserRepository { implicit val tryUserRepository: UserRepository[Try] = new UserRepository[Try] { def store(user: User): Try[Unit] = ??? def resolveByName(name: String): Try[Option[User]] = ??? } }
  • 36. Introduction to Tagless Final def prog[F[_]](userName: String) (implicit userRepo: UserRepository[F]): F[Option[User]] = userRepo.resolveByName(userName) prog[Try]("John") // scala.util.Try[Option[User]] = Success(None)
  • 38. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 39. よくある UserRepository インターフェースと実装例 trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] } // impl class UserRepositoryOnJDBC()(implicit ec: ExecutionContext) extends UserRepository[DBSession] { def store(user: User)(implicit ctx: DBSession): Future[Unit] = ??? def resolveByName(name: String)(implicit ctx: DBSession): Future[Option[User]] = ??? }
  • 40. Problem 1: 各メソッドが Context を暗黙の値として受け取る - Context を一つだけとることを前提としている - 暗黙的な値が複数必要だった場合にはそれらをまとめるコン テナを用意する必要がある - メソッド内では受け取った値から自分に必要なものを取り出す 作業が発生する Each method takes a context as an implicit parameter.
  • 41. Problem 1: 各メソッドが Context を暗黙の値として受け取る class UserRepositoryCtx(ec: ExecutionContext, dbSession: DBSession) // impl on database class UserRepositoryOnJDBC() extends UserRepository[UserRepositoryCtx] { def store(user: User)(implicit ctx: UserRepositoryCtx): Future[Unit] = { implicit val (ec, s) = (ctx.ec, ctx.dbSession) ??? // update } def resolveByName(name: String) (implicit ctx: UserRepositoryCtx): Future[Option[User]] = { implicit val (ec, s) = (ctx.ec, ctx.dbSession) ??? // select } }
  • 42. Problem 1: 各メソッドが Context を暗黙の値として受け取る case class DummyCtx() // impl by memory object UserRepositoryOnMemory extends UserRepository[DummyCtx]{ private val db = mutable.Map.empty[UserId, User] def store(user: User)(implicit ctx: DummyCtx): Future[Unit] = Future.successful(db.update(user.id, user)) def resolveByName(name: String)(implicit ctx: DummyCtx): Future[Option[User]] = Future.successful(db.collectFirst { case (_, u@User(_, n, _)) if n == name => u }) }
  • 43. Problem 2: メソッドの実行結果をどう表現するか いくつかのパターンが考えられる - 呼び出し側に責任を押し付け失敗を全て例外で throw - 例外を型で表現するために Try に包む - 非同期処理もありうるから Future にしておく - etc. How to express the return value as a type.
  • 44. Problem 2: メソッドの実行結果をどう表現するか // It’s simply! but throwable... trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Unit def resolveByName(name: String)(implicit ctx: Ctx): Option[User] } // It’s not throwable! but blocking only... trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Try[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Try[Option[User]] } // It’s non blocking! but needs ExecutionContext in Ctx... trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] }
  • 45. つまり、どうすればいいのか UserRepository インターフェース内に実装側の都合を一切持ち 込まないというのが重要 - Context という概念を UserRepository 内に持ち込まない - Context を暗黙の値にとるような前提の実装をやめる - メソッドの実行方法や失敗の表現など、まるっと含めて返り値 の型を抽象的に表現する Do not bring implementation related things to UserRepository interface.
  • 46. Remove implicit parameter trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] }
  • 47. Remove implicit parameter trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] } // 1. remove implicit trait UserRepository[Ctx] { def store(user: User)(ctx: Ctx): Future[Unit] def resolveByName(name: String)(ctx: Ctx): Future[Option[User]] }
  • 48. Remove implicit parameter trait UserRepository[Ctx] { def store(user: User)(implicit ctx: Ctx): Future[Unit] def resolveByName(name: String)(implicit ctx: Ctx): Future[Option[User]] } // 1. remove implicit trait UserRepository[Ctx] { def store(user: User)(ctx: Ctx): Future[Unit] def resolveByName(name: String)(ctx: Ctx): Future[Option[User]] } // 2. modify return function trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] }
  • 49. Remove implicit parameter trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] } // easy to use implicit parameter within impl. class UserRepositoryOnJDBC(implicit ec: ExecutionContext) extends UserRepository[DBSession] { def store(user: User): DBSession => Future[Unit] = { implicit dbSession => ??? } def resolveByName(name: String): DBSession => Future[Option[User]] = { implicit dbSession => ??? } }
  • 50. Really need Context? trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] }
  • 51. Really need Context? trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] } // 1. create type alias named `Exec` and replace return type trait UserRepository[Ctx] { type Exec[R] = Ctx => Future[R] def store(user: User): Exec[Unit] def resolveByName(name: String): Exec[Option[User]] }
  • 52. Really need Context? trait UserRepository[Ctx] { def store(user: User): Ctx => Future[Unit] def resolveByName(name: String): Ctx => Future[Option[User]] } // 1. create type alias named `Exec` and replace return type trait UserRepository[Ctx] { type Exec[R] = Ctx => Future[R] def store(user: User): Exec[Unit] def resolveByName(name: String): Exec[Option[User]] } // 2. add `F[_]` type parameter and remove `Exec` trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] }
  • 53. It’s more abstract interface trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] }
  • 54. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 55. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] }
  • 56. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] } type Query[A] = Reader[DBSession, A] object UserRepositoryOnJDBC { implicit def queryUserRepository: UserRepository[Query] = new UserRepository[Query] { def store(user: User): Query[Unit] = Reader { implicit dbSession => ??? } def resolveByName(name: String): Query[Option[User]] = Reader { implicit dbSession => ??? } } }
  • 57. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] } type AsyncQuery[A] = ReaderT[Task, DBSession, A] object UserRepositoryOnJDBC { implicit def asyncQueryUserRepository: UserRepository[AsyncQuery] = new UserRepository[AsyncQuery] { def store(user: User): AsyncQuery[Unit] = ReaderT { implicit dbSession => Task(???) } def resolveByName(name: String): AsyncQuery[Option[User]] = ReaderT { implicit dbSession => Task(???) } } }
  • 58. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] } type UserRepositoryFree[A] = Free[UserRepositoryOp, A] object UserRepositoryOnJDBC { implicit def freeUserRepository: UserRepository[UserRepositoryFree] = new UserRepository[UserRepositoryFree] { def store(user: User): UserRepositoryFree[Unit] = UserRepositoryOp.store(user) def resolveByName(name: String): UserRepositoryFree[Option[User]] = UserRepositoryOp.resolveByName(name) } }
  • 59. UserRepository implementation trait UserRepository[F[_]] { def store(user: User): F[Unit] def resolveByName(name: String): F[Option[User]] } type ConnectionIO[A] = Free[ConnectionOp, A] object UserRepositoryOnJDBC { implicit def doobieUserRepository: UserRepository[ConnectionIO] = new UserRepository[ConnectionIO] { def store(user: User): ConnectionIO[Unit] = sql"...".update.run.map(_ => ()) def resolveByName(name: String): ConnectionIO[Option[User]] = sql"...".query[User].option } }
  • 60. UserRepository implementation object UserRepository { def apply[F[_]](implicit F: UserRepository[F]): UserRepository[F] = F } val dbSession = new DBSession() type Query[A] = Reader[DBSession, A] UserRepository[Query].resolveByName("John Doe") .run(dbSession) type AsyncQuery[A] = ReaderT[Task, DBSession, A] UserRepository[AsyncQuery].resolveByName("John Doe") .run(dbSession).runAsync type UserRepositoryFree[A] = Free[UserRepositoryOp, A] UserRepository[UserRepositoryFree].resolveByName("John Doe") .foldMap(futureInterp(dbSession))
  • 61. UserRepository implementation object UserRepository { def apply[F[_]](implicit F: UserRepository[F]): UserRepository[F] = F } val xa = Transactor.fromDriverManager[Task](...) // for doobie type ConnectionIO[A] = Free[ConnectionOp, A] UserRepository[ConnectionIO].resolveByName("John Doe") .transact(xa).runAsync
  • 62. UserController implementation object UserController { def getUser[F[_]: Monad: UserRepository](id: Long): F[Result] = UserRepository[F].resolveBy(id).map { case Some(user) => Results.Ok(GetUserResponse.from(user).toJson) case None => Results.NotFound } }
  • 63. UserController implementation class UserController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... import UserRepositoryOnJDBC._ // impls def getUser(id: Long) = Action { UserController.getUser[Query](id).run(dbSession) } def getUserAsync(id: Long) = Action.async { UserController.getUser[AsyncQuery](id).run(dbSession).runAsync } }
  • 64. UserController testing class UserRepositoryMock extends UserRepository[Id] { def store(user: User): Id[Unit] = ??? def resolveBy(id: Long): Id[Option[User]] = ??? def resolveByName(name: String): Id[Option[User]] = ??? }
  • 65. UserController testing class UserControllerSpec extends PlaySpec { "UserController GET" should { "get user from static controller" in { implicit val mock = new UserRepositoryMock { override def resolveBy(id: Long): Option[User] = if (id == 100) Some(User(100, "John")) else None } UserController.getUser[Id](100).header.status mustBe OK UserController.getUser[Id](999).header.status mustBe NOT_FOUND } } }
  • 66. UserController testing object UserController { // ... def storeAndGet[F[_]: Monad: UserRepository](name: String): F[Result] = for { _ <- UserRepository[F].store(User(Random.nextLong(), name)) user <- UserRepository[F].resolveByName(name) } yield { user match { case Some(u) => Results.Ok(GetUserResponse.from(u).toJson) case None => Results.InternalServerError } } }
  • 67. UserController testing type DB = List[User] class UserRepositoryStateMock extends UserRepository[State[DB, ?]] { def store(user: User): State[DB, Unit] = ??? def resolveBy(id: Long): State[DB, Option[User]] = ??? def resolveByName(name: String): State[DB, Option[User]] = ??? }
  • 68. UserController testing class UserControllerSpec extends PlaySpec { "UserController GET" should { "store and get user from static controller" in { implicit val mock = new UserRepositoryStateMock { override def store(user: User): State[DB, Unit] = State(db => (user :: db, ())) override def resolveByName(name: String): State[DB, Option[User]] = State.inspect(_.find(_.name == name)) } UserController.storeAndGet[State[DB, ?]]("John") .runEmptyA.value.header.status mustBe OK } }
  • 69. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 70. F[_] が異なる場合は for 式で合成できない object MessageController { def getUserMessages[F[_]: Monad: MessageRepository: UserRepository]( userId: Long ): F[Result] = for { user <- UserRepository[F].resolveBy(userId) messages <- MessageRepository[F].resolveAllByUserId(userId) } yield { user match { case Some(u) => Results.Ok(GetMessagesResponse.from(u, messages).toJson) case None => Results.NotFound } } }
  • 71. F[_] が異なる場合は for 式で合成できない type AsyncQuery[A] = ReaderT[Task, DBSession, A] type AsyncCommand[A] = ReaderT[Task, RedisConnection, A] object MessageRepositoryOnJDBC { // MessageRepository[AsyncQuery] is not implemented implicit def redisConnectionMessageRepository: MessageRepository[AsyncCommand] = new MessageRepository[AsyncCommand] { def resolveBy(id: Long): AsyncCommand[Option[Message]] = ??? def store(message: Message): AsyncCommand[Unit] = ??? def resolveAllByUserId(userId: Long): AsyncCommand[List[Message]] = ??? } }
  • 72. F[_] が異なる場合は for 式で合成できない [pfpfap] $ compile [info] Compiling 1 Scala source to /Users/aoiroaoino/git/pfpf/bbs/target/scala-2.12/classes … ... [error] /Users/aoiroaoino/git/pfpf/bbs/app/controllers/MessageController.scala:26:50: could not find implicit value for evidence parameter of type models.repository.MessageRepository[models.AsyncQuery] [error] MessageController.getUserMessages[AsyncQuery](userId).run(dbSession).runAsync [error] ^
  • 73. F[_] が異なる場合は for 式で合成できない いくつかの解決策がある - Monad を積み上げる - F[_] を具体的な型にしてから controller 合成する - 自然変換を使って型を揃える There are some solutions. Stacking Monads, composing concrete types, Natural Transformation.
  • 74. Stacking Monads class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type SpecialAction[A] = ReaderT[ReaderT[Task, DBSession, ?], RedisConnection, A] def getUserMessagesSM(userId: Long) = Action.async { MessageController.getUserMessages[SpecialAction](userId) .run(redisConnection) // RedisConnection => ReaderT[Task, DBSession, A] .run(dbSession) // DBSession => Task[A] .runAsync // CancelableFuture[A] } }
  • 75. Composing concrete types class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... def getUserMessages(userId: Long) = Action.async { (for { user <- UserRepository[AsyncQuery].resolveBy(userId).run(dbSession) messages <- MessageRepository[AsyncCommand].resolveAllByUserId(userId) .run(redisConnection) } yield { user match { case Some(u) => Results.Ok(GetMessagesResponse.from(u, messages).toJson) case None => Results.NotFound } }).runAsync } }
  • 76. Use natural transformation and mapK() class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type AsyncQuery[A] = ReaderT[Task, DBSession, A] type AsyncCommand[A] = ReaderT[Task, RedisConnection, A] // common type of AsyncQuery and AsyncCommand type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A] def getUserMessages(userId: Long) = Action.async { MessageController.getUserMessages[Exec](userId) .run((dbSession, redisConnection)).runAsync } }
  • 77. Use natural transformation and mapK() trait MessageRepository[F[_]] { def store(message: Message): F[Unit] def resolveBy(id: Long): F[Option[Message]] def resolveAllByUserId(userId: Long): F[List[Message]] def mapK[G[_]](fk: F ~> G): MessageRepository[G] = new MessageRepository[G] { def store(message: Message): G[Unit] = fk(self.store(message)) def resolveBy(id: Long): G[Option[Message]] = fk(self.resolveBy(id)) def resolveAllByUserId(userId: Long): G[List[Message]] = fk(self.resolveAllByUserId(userId)) } }
  • 78. Use natural transformation and mapK() class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A] val asyncQueryToExec: AsyncQuery ~> Exec = new (AsyncQuery ~> Exec) { def apply[A](aq: AsyncQuery[A]): Exec[A] = ReaderT { case (dbSession, _) => aq.run(dbSessioin) } } val asyncCommandToExec: AsyncCommand ~> Exec = new (AsyncCommand ~> Exec) { def apply[A](ac: AsyncCommand[A]): Exec[A] = ReaderT { case (_, redisConn) => ac.run(redisConn) } } }
  • 79. Use natural transformation and mapK() class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A] val asyncQueryToExec: AsyncQuery ~> Exec = ... val asyncCommandToExec: AsyncCommand ~> Exec = ... implicit val execUserRepository: UserRepository[Exec] = UserRepository[AsyncQuery].mapK(asyncQueryToExec) implicit val execMessageRepository: MessageRepository[Exec] = MessageRepository[AsyncCommand].mapK(asyncCommandToExec) }
  • 80. Use natural transformation and mapK() class MessageController @Inject()(cc: ControllerComponents, ec: ExecutionContext) extends AbstractController(cc) { // ... type Exec[A] = ReaderT[Task, (DBSession, RedisConnection), A] val asyncQueryToExec: AsyncQuery ~> Exec = ... val asyncCommandToExec: AsyncCommand ~> Exec = ... implicit val execUserRepository: UserRepository[Exec] = ... implicit val execMessageRepository: MessageRepository[Exec] = ... def getUserMessages(userId: Long) = Action.async { MessageController.getUserMessages[Exec](userId) .run((dbSession, redisConnection)).runAsync } }
  • 81. Agenda Introduction - Play Framework with Google Guice - Purely functional separation of program & execution What’s Tagless Final style? - Overview - It’s your neighbor Tagless Final style practice in Play Framework - Purely, so useful! - But, No Silver Bullet Conclusion
  • 82. Conclusion Google Guice でアプリを全て組み立てる必要はない - プログラムとインタープリタの分離をする方法はいくつもあ る。 - Reader や Free もいいけど直接使うと不便さもある。 There are many ways to separate program & interpreter.
  • 83. Conclusion Tagless Final 便利 - 手で書いても苦にならない程度のコード量 - 複数の DSL を比較的簡単に合成できる - F[_] が異なる場合に一工夫必要。銀の弾丸ではない。 - Play Framework に限らずいろんな場面で使える! Tagless final is useful because it has less boilerplate.