!SLIDE
@markhibberd さんの Endo の話がわかりやすかったので、勝手に日本語の説明をつけたスライドを作った
- http://mth.io/talks/patterns-in-types-ylj
- https://github.com/markhibberd/lambdajam-patterns-in-types
!SLIDE
まず、Httpを処理をする以下のようなコードがあったとします
class GetUser extends HttpService {
def service[A](req: HttpRequest, res: HttpResponse) = {
val name = res.getCookie("HEY_I_DIG_SECURITY")
val result = impl.getUser(name)
val json = Json.render(result)
res.setHeader("content-type", "application/json")
res.setHeader("content-length", json.length)
res.setBody(json)
res.setStatusCode(200)
Log.info("200 %s".format(json))
}
}!SLIDE
HttpResponse というimmutableなcase classを作って、以下のように関数型に書き換えてみましょう!
case class HttpResponse(
code: Int,
body: String,
headers: Vector[(String, String)]
)
def status(code: Int, r: HttpResponse): HttpResponse =
r.copy(code = code)
def ok(r: HttpResponse): HttpResponse =
status(200, r)
def header(n: String, v: String, r: HttpResponse): HttpResponse =
r.copy(headers = r.headers :+ (n, v))
def contentType(mime: String, r: HttpResponse): HttpResponse =
header("content-type", mime, r)!SLIDE
関数本体を省略して、引数と戻り値のシグネチャだけを表示してみると、以下のようになります。
def status(code: Int, r: HttpResponse): HttpResponse
def ok(r: HttpResponse): HttpResponse
def header(n: String, v: String, r: HttpResponse): HttpResponse
def contentType(mime: String, r: HttpResponse): HttpResponse
def applicationJson(r: HttpResponse): HttpResponse!SLIDE
これを使って、最初のコードを書き換えることはできますが、綺麗に合成できてないですよね?
def jsonOk(json: String, r0: HttpResponse): HttpResponse = {
val r1 = ok(r0)
val r2 = applicationJson(r1)
val r3 = header("content-length", json.getBytes.length, r2)
r3
}!SLIDE
andThen を使ったとしても、微妙に不恰好です
def jsonOk(json: String, r: HttpResponse): HttpResponse =
ok(_).andThen(
applicationJson(_)
).andThen(
header("content-length", json.getBytes.length, _)
).apply(r)!SLIDE
すべての関数は、以下のように必ず HttpResponse を引数(の一つ)にとって、また HttpResponse を返しています。
HttpResponse => HttpResponse!SLIDE
このパターンを表すために、HttpResponder というcase classを作ってみましょう!
case class HttpResponder(run: HttpResponse => HttpResponse)!SLIDE
HttpResponder 同士を合成するために、 ~> という合成のための演算子を追加します。
(ところでこれは、わざと unfilteredと同じ にしているのだろうか)
case class HttpResponder(run: HttpResponse => HttpResponse) {
def ~>(other: HttpResponder) =
HttpResponder(run andThen other.run)
}!SLIDE
HttpResponder を使ってそれぞれの関数を書き換えると、こうなります
def status(code: Int): HttpResponder =
HttpResponder(_.copy(code = code))
def ok: HttpResponder =
status(200)
def header(n: String, v: String): HttpResponder =
HttpResponder(r =>
r.copy(headers = r.headers :+ (n, v)))
def contentType(mime: String): HttpResponsder =
setHeader("content-type", mime)
def applicationJson: HttpResponder =
contentType("application/json")!SLIDE
そうすると、最終的に以下のように綺麗に合成できますね!
def jsonOk(json: String): HttpResponder =
ok ~> applicationJson ~>
header("content-length", json.getBytes.length)!SLIDE
以上の、HttpResponder のようなパターンを表すものは、実はすでに存在しています
!SLIDE
それが、Endo です(ドヤッ
case class Endo[A](run: A => A)Endoとは、Endomorphisms の略- 参考: scalaz の Endo
!SLIDE
Endo は、以下のように Monoid になります
implicit def EndoMonoid[A]: Monoid[Endo[A]] =
new Monoid[Endo[A]] {
def append(f1: Endo[A], f2: => Endo[A]) =
Endo(f1.run compose f2.run)
def zero =
Endo[A](x => x)
}!SLIDE
そうすると最終的に HttpResponder は、単に Endo[HttpResponse] になり、 ~> というのは、単にMonoidにおける|+|の演算として表すことができます!
import scalaz._, Scalaz._
type HttpResponder = Endo[HttpResponse]
def jsonOk(json: String): HttpResponder =
ok |+| applicationJson |+|
header("content-length", json.getBytes.length)!SLIDE
-
本当は、この後
StateやReaderやWriterの話が続くけど、Endoに関しては終わり -
元のスライドの続きよみたい人はここから http://mth.io/talks/patterns-in-types-ylj/#/21