Hatena::ブログ(Diary)

case class HatenaDiary(id: Symbol = ’seratch2) RSSフィード Twitter

 

2037-12-31

About

Akasaka.scala #akskscala

毎週木曜19:00〜@赤坂インターシティ10F(溜池山王)で Scala の勉強会をやっています。

詳細は以下のサイトをご覧ください。

http://akskscala.github.com/


Scala の入門記事一覧

ある程度 Java がわかる初心者の方向けに簡潔にまとめる事を目的に書いています。

Scala の入門記事一覧


Scala School 意訳一覧

Scala School の抄訳と原文で説明があまりない点を少し補足しています。

Scala School 意訳一覧


Scala 言語仕様読書会メモ

Akasaka.scala でやっている言語仕様読書会の記録です。

Scala 言語仕様読書会メモ

2012-02-23

Windows (win32) 環境での Ruby 1.9.3-p125 セットアップ

2012/02/23 時点の情報です

もしこの記事が古くなっていたら、もっといい方法があるかもしれません。

RVM?

Windows なので RVM とかはない・・ですよね。

RubyInstaller

最近はとりあえず RubyInstaller for Windows を使えばハマることなくセットアップできます。

http://rubyinstaller.org/downloads/

手順

2012/02/23 時点なら RubyInstallers から「Ruby 1.9.3-p125」を選択してダウンロードした exe ファイルを実行。

実行完了したら Path が通ってることを確認。

$ ruby -v
ruby 1.9.3p125 (2012-02-16) [i386-mingw32]

gem でいろいろやる前に DevKit を入手します。

先ほどのダウンロードページに戻って「DEVELOPMENT KIT」のところにある exe ファイルをダウンロードします。

この記事時点だと「DevKit-tdm-32-4.5.2-20111229-1559-sfx.exe」というのが最新なので、これをダウンロードします。

このファイルは自己解凍書庫なので、分かりやすい名前のディレクトリとして保存します。

なお、DevKit を置く場所のパスはセットアップ時に設定として書き込まれるので、あまり適当な場所にしないほうがよいと思います。

DevKit のトップディレクトリに「dk.rb」というセットアップ用の Ruby スクリプトがあります。以下のように実行します。

$ ruby dk.rb init
[INFO] found RubyInstaller v1.9.3 at C:/dev/Ruby/Ruby193

Initialization complete! Please review and modify the auto-generated
'config.yml' file to ensure it contains the root directories to all
of the installed Rubies you want enhanced by the DevKit.

$ ruby dk.rb install
[INFO] Updating convenience notice gem override for 'C:/dev/Ruby/Ruby193'
[INFO] Installing 'C:/dev/Ruby/Ruby193/lib/ruby/site_ruby/devkit.rb'

「lib/ruby/site_ruby/devkit.rb」にはこのディレクトリへのパスが書き込まれています。

DevKit をセットアップしていない場合「gem install json」などで以下のようなエラーが発生しますが

$ gem update
Updating installed gems
Updating json
Fetching: json-1.6.5.gem (100%)
ERROR:  Error installing json:
        The 'json' native gem requires installed build tools.

Please update your PATH to include build tools or download the DevKit
from 'http://rubyinstaller.org/downloads' and follow the instructions
at 'http://github.com/oneclick/rubyinstaller/wiki/Development-Kit'

そうならなければ OK です。

$ gem update
Updating installed gems
Updating json
...

2012-02-19

Scalatra 2.0.3 ソースコードリーディング

Scalatra 2.0.3 を読みました

この記事は Scalatra 2.0.3 を対象に書かれています。


ScalatraServlet と ScalatraFilter

まず Scalatra のコントローラの基底 class/trait には ScalatraServlet と ScalatraFilter の二つがありますが、どのように使い分けるのでしょうか。

ScalatraServlet
abstract class ScalatraServlet
  extends javax.servlet.http.HttpServlet
  with org.scalatra.ScalatraKernel
  with org.scalatra.Initializable

Scalatra DSL の javax.servlet.http.HttpServlet を継承した実装です。

ほとんどの Scalatra アプリケーションではこの基底クラスを使うことが推奨されます。

以下のような場合は ScalatraServlet を使いましょう。

  • Scalatra のルーティングをあなたのアプリケーションの subcontext (コンテキストパスが ROOT でない)で実行したい場合
  • routes にマッチしなかったリクエストを完全にコントロールしたい場合
  • 単に静的コンテンツの serve を Filter でやりたいと考えた場合(それは ScalatraServlet でもできます)
  • ScalatraServlet と ScalatraFilter の違いがよくわかっていない場合

上記は scaladoc の抄訳です。ここで書かれている内容は実際に web.xml のサンプルを見ると分かりやすいと思います。

https://github.com/scalatra/scalatra/blob/master/example/src/main/webapp/WEB-INF/web.xml

ScalatraFilter
trait ScalatraFilter 
  extends javax.servlet.Filter 
  with org.scalatra.ScalatraKernel
  with org.scalatra.Initializable

Scalatra DSL の javax.servlet.Filter を implements した実装です。

以下のような場合には ScalatraServlet よりも ScalatraFilter を使う方がよいかもしれません。

  • URL 空間を他の servlet や filter と共有していて routes にマッチしなかったリクエストの処理を他に委譲したい場合(これはレガシーアプリケーションとの統合にとても便利です)

ScalatraServlet と違って routes にマッチしなかった場合に HTTP ステータス 404 や 405 をレスポンスしません。

代わりに FilterChain で処理を委譲します。ScaltraFilter に半信半疑な場合は ScalatraServlet を使いましょう。


ScalatraKernel

ScalatraServlet も ScalatraFilter も ScalatraKernel のサブ型になっていて、この trait が Scalatra のコア部分です。

この trait の実装が実際にどのようになっているか、追ってみます。

trait ScalatraKernel 
  extends org.scalatra.Handler 
  with org.scalatra.CoreDsl 
  with org.scalatra.Initializable 
  with org.scalatra.servlet.ServletApiImplicits

ルーティングの仕組み (Scalatra 2.0.3)

org.scalatra.ScalatraKernel の中身をみると「get("/foo")」が「GET /foo」にどのようにルーティングされるかがわかります。

class App extends ScalatraServlet {
  get("/foo") { ... }
}

「"/foo"」が暗黙の型変換によって RouteMatcher に変換されて

protected implicit def string2RouteMatcher(path: String): RouteMatcher = new SinatraRouteMatcher(path, requestPath)

実際には、この get が呼ばれます。addRoute では Route を生成して ScalatraKernel 内に保持している routes(RouteRegistry) へ登録します。

def get(routeMatchers: RouteMatcher*)(action: => Any) = addRoute(Get, routeMatchers, action)

この処理を単純化したサンプルを書いてみました。get(path: String) というメソッドは定義されていませんが正常に動作することが分かります。

case class Path(path: String)
trait RoutingSupport {
  protected implicit def string2Path(path: String) = Path(path)
  def get(path: Path)(action: => Any) = {
    println("added new route: (" + path + " -> " + action + ")")
  }
}

class Application extends RoutingSupport {
  get("/foo") {
    "200 ok"
  }
}
val app = new Application
// "added new route: (Path(/foo) -> 200 ok)" と出力される

また、String 型だけでなく Boolean 型の名前渡し、 PathPattern 型、 Regex 型にも暗黙の型変換が定義されています。

protected implicit def booleanBlock2RouteMatcher(block: => Boolean): RouteMatcher = new BooleanBlockRouteMatcher(block)

Boolean 型の名前渡しは、以下のような例です。

get("/foo", request.getRemoteHost == "127.0.0.1", request.getRemoteUser == "admin") {
  // "/foo" への localhost からのリクエスト、かつユーザが admin の場合のみ
}

ルーティングの仕組み (Scalatra 2.1.0-SNAPSHOT)

リリースまでにまだ変更があるかもしれませんが、少しリファクタリングされています。

  • org.scalatra.ScalatraKernel
  • org.scalatra.ScalatraKernel.Routing

ScalatraKernel.Routing に暗黙の型変換と get post のような routing の DSL インタフェースが分離されています。

また「"/foo"」が暗黙の型変換によって RouteMatcher でなく RouteTransformer に変換されるようになりました。

protected implicit def string2RouteTransformer(path: String): RouteTransformer = Route.appendMatcher(path)

RouteTransformer は org.scalatra の package object に、Route を受け取って Route を返す関数への型エイリアスとして定義されています。

type RouteTransformer = (Route => Route)

get の引数型も RouteMatcher* から RouteTransformer* に変更になっていますが

def get(transformers: RouteTransformer*)(action: => Any): Route = addRoute(Get, transformers, action)

RouteMatcher も RouteTransformer に型変換することで互換性が保たれています。

protected implicit def routeMatcher2RouteTransformer(matcher: RouteMatcher): RouteTransformer = Route.appendMatcher(matcher)

リクエストを受けてからレスポンスするまでの流れ

ScalatraServlet でも ScalatraFilter でも ScalatraKernel に定義されたリクエストの処理は以下のメソッドが呼び出されるようになっています。

def handle(request: HttpServletRequest, response: HttpServletResponse)

内部で executeRoutes を呼び出して、さらに routes から実行する action を探し出して実行する runRoutes を呼んでいます。

protected def executeRoutes(): Any = {
  val result = try {
    runFilters(routes.beforeFilters) // before の処理を適用
    val actionResult = runRoutes(routes(request.method)).headOption // action の実行
    actionResult orElse matchOtherMethods() getOrElse doNotFound() // matchOtherMethods は 405、doNotFound は 404 を返す
  } catch {
    case e: HaltException => renderHaltException(e)
    case e => errorHandler(e) // 例外のハンドリング
  } finally {
    runFilters(routes.afterFilters) // after の処理を適用
  }
  renderResponse(result) // レスポンスボディのレンダリング
}

runRoutes の中では HTTP メソッドで絞り込まれた routes の中からマッチするものを探して invoke します。

protected def runRoutes(routes: Traversable[Route]) =
  for {
    route <- routes.toStream // toStream makes it lazy so we stop after match
    matchedRoute <- route()
    actionResult <- invoke(matchedRoute)
  } yield actionResult

「toStream makes it lazy so we after match」はこんな感じで、汎用的に応用できそうなテクニックです。

scala> for ( i <- Seq(1,2,3).toStream; j <- Seq(i*i) if i == 2 ) yield j
res0: scala.collection.immutable.Stream[Int] = Stream(4, ?)

このメソッドの結果は Stream になっているので executeRoutes ではそこから headOption で先頭を Option[Any] として取り出しています。

val actionResult = runRoutes(routes(request.method)).headOption

runRoutes の中の invoke では

protected def invoke(matchedRoute: MatchedRoute) =
  withRouteMultiParams(Some(matchedRoute)) {
    liftAction(matchedRoute.action)
  }

まず request(RichRequest*1 )に withRouteMultiParams で URL に埋まっているパラメータを追加して

protected def withRouteMultiParams[S](matchedRoute: Option[MatchedRoute])(thunk: => S): S = {
  val originalParams = multiParams
  request(MultiParamsKey) = originalParams ++ matchedRoute.map(_.multiParams).getOrElse(Map.empty)
  try { thunk } finally { request(MultiParamsKey) = originalParams }
}

liftAction で MatchedRoute から取り出した action: => Any を実行し結果を Optio[Any] で受け取ります。

private def liftAction(action: Action): Option[Any] = try { Some(action()) } catch { case e: PassException => None }

再び executeRoutes に戻って、最後のレスポンスボディのレンダリング部分です。

protected def executeRoutes(): Any = {
  val result = try { ... }
  // ...
  renderResponse(result)
}

renderResponse は actionResult を renderResponseBody に渡して

protected def renderResponse(actionResult: Any) {
  if (contentType == null) contentTypeInferrer.lift(actionResult) foreach { contentType = _ }
  renderResponseBody(actionResult)
}

renderResponseBody は loop に Unit 型が渡されるまで renderPipeline.lift(a) を引数に loop を再帰呼び出しします。

protected def renderResponseBody(actionResult: Any) {
  @tailrec def loop(ar: Any): Any = ar match {
    case r: Unit =>
    case a => loop(renderPipeline.lift(a) getOrElse ())
  }
  loop(actionResult)
}

renderPipeline の取得は以下の通りです。この RenderPipeline 型とは何者かというと PartialFunction[Any, Any] 型のエイリアスです*2

protected def renderPipeline: RenderPipeline = {
  case bytes: Array[Byte] => response.getOutputStream.write(bytes)
  case file: File => using(new FileInputStream(file)) { in => zeroCopy(in, response.getOutputStream) }
  case _: Unit => // If an action returns Unit, it assumes responsibility for the response
  case x: Any  => response.getWriter.print(x.toString)
}

renderResponseBody の renderPipeline.lift(a) は PartialFunction#lift(Any) の呼び出しです。

PartialFunction の apply はマッチしなかったら MatchError を throw しますが、lift はマッチすれば Some を返し、そうでなければ None を返します。

scala> val pf: PartialFunction[Any, Any] = { case s: String => s }
pf: PartialFunction[Any,Any] = <function1>

scala> pf.lift(123)
res5: Option[Any] = None

scala> pf.lift("abc")
res6: Option[Any] = Some(abc)

http://www.scala-lang.org/api/current/index.html#scala.PartialFunction


たとえば renderPipeline 内の「case x: Any => response.getWriter.print(x.toString)」にマッチすると Option にくるまれた Unit が返ります*3

scala> val pf: PartialFunction[Any, Any] = { case a: Any => println(a) }
pf: PartialFunction[Any,Any] = <function1>

scala> pf.lift("aaa")
aaa
res2: Option[Any] = Some(())

loop には Unit 型が渡ることになるので、ここが renderResponseBody での loop の再帰呼び出しの底になります。

2.0.3 の実装でいうと actionResult が Array[Byte] 型、 File 型以外はすべて #toString の結果がそのまま出力されるということになります。


templateEngine の仕組み(ScalateSupport)

Scalate を使う場合 ScalateSupport という trait を mixin します。この trait は ScalatraKernel のサブ型になっています*4

以下のフィールドが定義されているので、これを呼び出して使います。

protected[scalatra] var templateEngine: TemplateEngine = _

createTemplateEngine というメソッドによって初期化されます。

ServletTemplate は Scalate のクラスで ScalatraTemplateEngine は ScalateSupport 内に定義されている拡張部分です。

abstract override def initialize(config: Config) {
  super.initialize(config)
  templateEngine = createTemplateEngine(config)
}

protected def createTemplateEngine(config: Config): TemplateEngine =
  config match {
    case servletConfig: ServletConfig => new ServletTemplateEngine(servletConfig) with ScalatraTemplateEngine
    case filterConfig: FilterConfig => new ServletTemplateEngine(filterConfig) with ScalatraTemplateEngine
    case _ =>
      // Don't know how to convert your Config to something that
      // ServletTemplateEngine can accept, so fall back to a TemplateEngine
      new TemplateEngine with ScalatraTemplateEngine
    }

Scalatra の action 内で以下のように Scalate を呼び出すことができますが

layoutTemplate("index.ssp", Map("foo" -> "uno", "bar" -> "dos"))
ssp("index.ssp", Map("foo" -> "uno", "bar" -> "dos"))

いずれも中では Scalate の TemplateEngine#layout をそのまま呼んでいるだけです。

http://scalate.fusesource.org/maven/1.5.3/scalate-core/scaladocs/org/fusesource/scalate/servlet/ServletTemplateEngine.html

templateEngine.layout("index.ssp", Map("foo" -> "uno", "bar" -> "dos"))

結果として String 型が返ってきて、それが ScalatraKernel#renderResponseString でそのまま出力されることになります。

*1:HttpServletRequest の拡張でそれ自身が mutable な Map になっています

*2:org.scalatra の package object に定義されています

*3:override しなかった場合 Array[Byte] や File の場合も Unit を返しているので loop は一階しか再帰しませんが

*4:self-type で ScalatraKernel 型をとる trait に書き換えることができそうです

2012-02-18

scala.concurrent.ops を使ったサンプル例

Scalaのバージョン

この記事が対象とするScalaのバージョンは「2.9.1.final」です。

API document

http://www.scala-lang.org/api/2.9.1/scala/concurrent/ops$.html


並行処理(spawn)

spawn{ } の中はその場限りの別スレッドで処理されます。

import scala.concurrent.ops._
spawn {
  Thread.sleep(3000L)
  println("by spawn")
}
Thread.sleep(2000L)
println("Maybe before spawn...")
Thread.sleep(2000L)

// Maybe before spawn...
// by spawn

上記の例だと構文っぽく見えますが、spawn は ops という Singleton オブジェクトに定義されているメソッド引数は名前渡しの「=>Unit」)で、メソッド呼び出しっぽく書くと以下のようになります。

import concurrent.ops
ops.spawn({
  println("do something")
})

変数は値コピーではなく参照されるので、特に mutable な変数の共有は注意が必要です。

import concurrent.ops._
var hoge = "hoge" // mutableな変数
spawn {
  Thread.sleep(500L)
  println(hoge) // hoge
}
spawn {
  Thread.sleep(1000L)
  println(hoge) // HOGE
}
spawn {
  Thread.sleep(2000L)
  println(hoge) // HOGE
}
Thread.sleep(800L)
hoge = "HOGE"
Thread.sleep(2000L)

二つの処理を並行で実行する(par)

par[A,B](xp: => A, yp: => B): (A, B) は名前渡しで渡された二つの処理を並行で実行し、結果をタプルで返します。

import concurrent.ops._
val result: (String, Int) = par ({ Thread.sleep(1000L); "1" },  { Thread.sleep(2000L); 2 })

並行処理の待ち合わせ(future)

future{ } の中の処理結果を Future として受け取れます。apply で結果を取得する時は同期処理になります。

import scala.concurrent.ops._
val ftime = future {
  Thread.sleep(3000L)
  System.currentTimeMillis
}
val time = System.currentTimeMillis
assert(time < ftime()) // assert(time < ftime.apply)