もしこの記事が古くなっていたら、もっといい方法があるかもしれません。
Windows なので RVM とかはない・・ですよね。
最近はとりあえず 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 ...
この記事は Scalatra 2.0.3 を対象に書かれています。
まず Scalatra のコントローラの基底 class/trait には ScalatraServlet と ScalatraFilter の二つがありますが、どのように使い分けるのでしょうか。
abstract class ScalatraServlet extends javax.servlet.http.HttpServlet with org.scalatra.ScalatraKernel with org.scalatra.Initializable
Scalatra DSL の javax.servlet.http.HttpServlet を継承した実装です。
ほとんどの Scalatra アプリケーションではこの基底クラスを使うことが推奨されます。
以下のような場合は ScalatraServlet を使いましょう。
上記は scaladoc の抄訳です。ここで書かれている内容は実際に web.xml のサンプルを見ると分かりやすいと思います。
https://github.com/scalatra/scalatra/blob/master/example/src/main/webapp/WEB-INF/web.xml
trait ScalatraFilter extends javax.servlet.Filter with org.scalatra.ScalatraKernel with org.scalatra.Initializable
Scalatra DSL の javax.servlet.Filter を implements した実装です。
以下のような場合には ScalatraServlet よりも ScalatraFilter を使う方がよいかもしれません。
ScalatraServlet と違って routes にマッチしなかった場合に HTTP ステータス 404 や 405 をレスポンスしません。
代わりに FilterChain で処理を委譲します。ScaltraFilter に半信半疑な場合は ScalatraServlet を使いましょう。
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
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 の場合のみ }
リリースまでにまだ変更があるかもしれませんが、少しリファクタリングされています。
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 の結果がそのまま出力されるということになります。
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 をそのまま呼んでいるだけです。
templateEngine.layout("index.ssp", Map("foo" -> "uno", "bar" -> "dos"))
結果として String 型が返ってきて、それが ScalatraKernel#renderResponseString でそのまま出力されることになります。
ある程度 Java がわかる初心者の方向けに簡潔にまとめる事を目的に書いています。
scala.concurrent.ops を使ったサンプル例
Scala School の抄訳と原文で説明があまりない点を少し補足しています。
http://twitter.github.com/scala_school/index.html
Scala School 意訳(Basics continued)
Scala School 意訳(Pattern matching & functional composition)
Scala School 意訳(Type & polymorphism basics)
Scala School 意訳(Advanced types)
Scala School 意訳(More collections)
Scala School 意訳(Testing with specs)
Akasaka.scala でやっている言語仕様読書会の記録です。
この記事が対象とするScalaのバージョンは「2.9.1.final」です。
http://www.scala-lang.org/api/2.9.1/scala/concurrent/ops$.html
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[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 として受け取れます。apply で結果を取得する時は同期処理になります。
import scala.concurrent.ops._ val ftime = future { Thread.sleep(3000L) System.currentTimeMillis } val time = System.currentTimeMillis assert(time < ftime()) // assert(time < ftime.apply)