第11章では、関数を作る型、
(->) r
も、Functor
のインスタンスであることを見ました。
import cats._, cats.syntax.all._
val f = (_: Int) * 2
// f: Int => Int = <function1>
val g = (_: Int) + 10
// g: Int => Int = <function1>
(g map f)(8)
// res0: Int = 36
それから、関数はアプリカティブファンクターであることも見ましたね。これにより、関数が将来返すであろう値を、すでに持っているかのように演算できるようになりました。
{
val h = (f, g) mapN {_ + _}
h(3)
}
// res1: Int = 19
関数の型
(->) r
はファンクターであり、アプリカティブファンクターであるばかりでなく、モナドでもあります。これまでに登場したモナド値と同様、関数もまた文脈を持った値だとみなすことができるのです。関数にとっての文脈とは、値がまだ手元になく、値が欲しければその関数を別の何かに適用しないといけない、というものです。
この例題も実装してみよう:
{
val addStuff: Int => Int = for {
a <- (_: Int) * 2
b <- (_: Int) + 10
} yield a + b
addStuff(3)
}
// res2: Int = 19
(*2)
と(+10)
はどちらも3
に適用されます。実は、return (a+b)
も同じく3
に適用されるんですが、引数を無視して常にa+b
を返しています。そいういうわけで、関数モナドは Reader モナドとも呼ばれたりします。すべての関数が共通の情報を「読む」からです。
Reader
モナドは値が既にあるかのようなフリをさせてくれる。恐らくこれは1つのパラメータを受け取る関数でしか使えない。
2012年3月9日にあった nescala 2012 で Rúnar (@runarorama) さんが
Dead-Simple Dependency Injection
というトークを行った。そこで提示されたアイディアの一つは Reader
モナドを dependency injection
に使うというものだった。同年の 12月に YOW 2012 でそのトークを長くした
Lambda: The Ultimate Dependency Injection Framework
も行われた。
翌 2013年に Jason Arhart さんが書いた
Scrap Your Cake Pattern Boilerplate: Dependency Injection Using the Reader Monad
に基づいた例をここでは使うことにする。
まず、ユーザを表す case class と、ユーザを取得するためのデータストアを抽象化した trait があるとする。
case class User(id: Long, parentId: Long, name: String, email: String)
trait UserRepo {
def get(id: Long): User
def find(name: String): User
}
次に、UserRepo
trait の全ての演算に対してプリミティブ・リーダーを定義する:
trait Users {
def getUser(id: Long): UserRepo => User = {
case repo => repo.get(id)
}
def findUser(name: String): UserRepo => User = {
case repo => repo.find(name)
}
}
(ボイラープレートをぶち壊せとか言いつつ) これはボイラープレートっぽい。一応、次。
プリミティブ・リーダーを合成することで、アプリケーションを含む他のリーダーを作ることができる。
object UserInfo extends Users {
def userInfo(name: String): UserRepo => Map[String, String] =
for {
user <- findUser(name)
boss <- getUser(user.parentId)
} yield Map(
"name" -> s"${user.name}",
"email" -> s"${user.email}",
"boss_name" -> s"${boss.name}"
)
}
trait Program {
def app: UserRepo => String =
for {
fredo <- UserInfo.userInfo("Fredo")
} yield fredo.toString
}
この app
を実行するためには、UserRepo
の実装を提供する何かが必要だ:
val testUsers = List(User(0, 0, "Vito", "vito@example.com"),
User(1, 0, "Michael", "michael@example.com"),
User(2, 0, "Fredo", "fredo@example.com"))
// testUsers: List[User] = List(
// User(id = 0L, parentId = 0L, name = "Vito", email = "vito@example.com"),
// User(id = 1L, parentId = 0L, name = "Michael", email = "michael@example.com"),
// User(id = 2L, parentId = 0L, name = "Fredo", email = "fredo@example.com")
// )
object Main extends Program {
def run: String = app(mkUserRepo)
def mkUserRepo: UserRepo = new UserRepo {
def get(id: Long): User = (testUsers find { _.id === id }).get
def find(name: String): User = (testUsers find { _.name === name }).get
}
}
Main.run
// res3: String = "Map(name -> Fredo, email -> fredo@example.com, boss_name -> Vito)"
ボスの名前が表示された。
for
内包表記の代わりに actM
を使ってみる:
object UserInfo extends Users {
import example.MonadSyntax._
def userInfo(name: String): UserRepo => Map[String, String] =
actM[UserRepo => *, Map[String, String]] {
val user = findUser(name).next
val boss = getUser(user.parentId).next
Map(
"name" -> s"${user.name}",
"email" -> s"${user.email}",
"boss_name" -> s"${boss.name}"
)
}
}
trait Program {
import example.MonadSyntax._
def app: UserRepo => String =
actM[UserRepo => *, String] {
val fredo = UserInfo.userInfo("Fredo").next
fredo.toString
}
}
object Main extends Program {
def run: String = app(mkUserRepo)
def mkUserRepo: UserRepo = new UserRepo {
def get(id: Long): User = (testUsers find { _.id === id }).get
def find(name: String): User = (testUsers find { _.name === name }).get
}
}
Main.run
// res5: String = "Map(name -> Fredo, email -> fredo@example.com, boss_name -> Vito)"
actM
ブロックの中は for
バージョンよりも自然な形に見えるけども、
型注釈が必要なせいで、多分こっちの方が使いづらいと思う。
今日はここまで。