2012年8月8日水曜日

手続きと抽象化とScalaz

夏休みです。
コード書きましょう。

現在、UnfilteredとJavaFX使ってなんか創ってます。

Unfiltered


コード例

import unfiltered.request._
import unfiltered.response._
object Server extends App {
val hello = unfiltered.netty.cycle.Planify {
case _ => ResponseString("hello world")
}
unfiltered.netty.Http(8080).plan(hello).run()
}
view raw app1.scala hosted with ❤ by GitHub
だいたいこんな感じ。

私はAsynchronousにHttpクライアント使ってほげほげしたいので、async版を使います。

import dispatch._
object Server extends App {
def hello(client: nio.Http) = unfiltered.netty.async.Planify {
case req => req.respond(ResponseString("hello world"))
}
val client = new nio.Http
unfiltered.netty.Http(8080).plan(hello(client)).run()
client.shutdown()
}
view raw app2.scala hosted with ❤ by GitHub
※このコードではHttpクライアント使ってないけど例なのでキニシナイ。

私のプロジェクトではこのサーバーを
  • アプリケーションとして起動
  • JavaFXの裏で起動
という2通りの方法が欲しいのです。

JavaFX


SwingはオワコンなのでJavaFX使いましょう。

コード例

import javafx.application.Application
import javafx.stage.Stage
class Client extends Application {
def start(stage: Stage) {
stage.show
}
}
object Client extends App {
Application launch classOf[Client]
}
view raw app3.scala hosted with ❤ by GitHub
クライアントを起動する前にサーバーを起動したいのですが、いろいろと問題があり、通常のrunメソッドによる起動は出来ません。

そこで、runメソッドで行われているstart, stop, destroyを直接呼ぶことにします。

object Client extends App {
val client = new nio.Http
val server = unfiltered.netty.Http(8000).plan(Server.hello(client))
server.start()
Application launch classOf[Client]
server.stop()
server.destroy()
client.shutdown()
}
view raw app4.scala hosted with ❤ by GitHub

抽象化


重複したところを考えます。

まずはサーバーを組み立てるところ。
ポートも自由に設定したい。

object Server extends App {
def hello(client: nio.Http) = unfiltered.netty.async.Planify {
case req => req.respond(ResponseString("hello world"))
}
def server(port: Int)(client: nio.Http) =
unfiltered.netty.Http(port).plan(hello(client))
val client = new nio.Http
server(8080)(client).run()
client.shutdown()
}
object Client extends App {
val client = new nio.Http
val server = Server.server(8000)(client)
server.start()
Application launch classOf[Client]
server.stop()
server.destroy()
client.shutdown()
}
view raw app5.scala hosted with ❤ by GitHub

次にnio.Httpのところ。
shutdownは自動でやって欲しい。
幸い、unfiltered.netty.HttpとApplication.launchはThreadをwaitしてくれるのでローンパターン的にします。
util.control.Exceptionを使うと、try - catch - finallyを生で書く必要がないことがわかりますね!
ナチュラルにScalaz使っていますが、>>>はandThenです。

import scalaz._, Scalaz._
import scala.util.control.Exception
object Server extends App {
def client[A](f: nio.Http => A) = {
val client = new nio.Http
(Exception ultimately client.shutdown())(f(client))
}
def hello(client: nio.Http) = unfiltered.netty.async.Planify {
case req => req.respond(ResponseString("hello world"))
}
def server(port: Int)(client: nio.Http) =
unfiltered.netty.Http(port).plan(hello(client))
client(server(8080) _ >>> (_.run()))
}
object Client extends App {
Server.client { c =>
val server = Server.server(8000)(c)
server.start()
Application launch classOf[Client]
server.stop()
server.destroy()
}
}
view raw app6.scala hosted with ❤ by GitHub

サーバーの方は十分綺麗になりました。
クライアントのサーバーの起動、終了の部分もローンパターンで書けます。

object Client extends App {
def run[A](a: => A)(server: unfiltered.netty.Http) = {
Exception ultimately {
server.stop()
server.destroy()
} apply {
server.start()
a
}
}
Server.client(Server.server(8000) _ >>> run(Application launch classOf[Client]))
}
view raw app7.scala hosted with ❤ by GitHub

綺麗になりました。

Scalaz


さて、ここまでが私的Scalaプログラミングですが、まあ、普通過ぎて面白くないですね。
そこで、IOモナドを用いて実装してみます。


まずはサーバーのコード。

import effect._
object Server extends SafeApp {
def hello(client: nio.Http) = unfiltered.netty.async.Planify {
case req => req.respond(ResponseString("hello world"))
}
def client[A](f: nio.Http => IO[A]) =
IO(new nio.Http).bracket(_.shutdown.point[IO])(f)
def server(port: Int)(client: nio.Http) =
unfiltered.netty.Http(port).plan(hello(client))
override def runc = client(server(8080) _ >>> (_.run.point[IO]))
}
view raw app8.scala hosted with ❤ by GitHub

SafeAppはIOモナドを暗黙的に実行するためのもので、runcをオーバーライドする事でアプリケーションを構築します。

IO#bracketはtry - finallyのようなもので、リソースを扱うときに使います。

次にクライアント。

object Client extends SafeApp {
def run[A](a: IO[A])(server: unfiltered.netty.Http) =
IO(server.start).bracket_(IO(server.stop) >> IO(server.destroy))(a)
override def runc =
Server.client(Server.server(8000) _ >>> run((Application launch classOf[Client]).point[IO]))
}
view raw app9.scala hosted with ❤ by GitHub

アンダーバーが付いた関数は、前の結果を捨てるという意味があります。
Haskellの習慣ですね。



はいおしまい。

IOモナドは便利なのですが、UnfilteredやJavaFXがIOモナド用いた設計をしているわけではないので、少しばかり面倒なところがあります。

告知


第二回スタートScalazやります、多分。

0 件のコメント:

コメントを投稿