Skip to content

Instantly share code, notes, and snippets.

@xuwei-k
Created May 19, 2013 14:13
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xuwei-k/5607773 to your computer and use it in GitHub Desktop.
Save xuwei-k/5607773 to your computer and use it in GitHub Desktop.
Scalaz Endo

!SLIDE

@markhibberd さんの Endo の話がわかりやすかったので、勝手に日本語の説明をつけたスライドを作った



!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)




!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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment