!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