giter8 & sbt new
最近はactivator newを使っている人が多いんだと思いますが、5年くらい前?はgiter8というツールが使われていました。 giter8は一時期Typesafe Stackにも含まれるほどだったんですが、なぜか見捨てられてしまいます。 その後皆さんご存知の通りactivator newに取って代わられ、開発も下火になり、知る人ぞ知るみたいな存在になってしまいましたが、少し前にpamflet、conscriptとともにfoundweekendsに拾われ、メンテナンスが継続されていくことになりました。
foundweekendsは@eed3si9nさんが作ったorganizationですが、@eed3si9nさんはsbtの開発者でもあります。そんなこんなでgiter8がsbtで使えるようになりました。sbt 0.13.13-RC1から new
コマンドが追加されたのですが、 new
コマンドはgiter8をサポートしています。
sbt 0.13.13-RC1のlauncherを使うとgiter8テンプレートからプロジェクトを作成することができます。
$ mkdir hello $ cd hello $ sbt new tototoshi/hello.g8 # https://github.com/tototoshi/hello.g8
さて、giter8を触ったことがない人も最近は多いと思うのでgiter8テンプレートの作り方も紹介しておこうと思います。 昔はレイアウトを作る少し面倒だったんですが、今はプロジェクト名の置換などの気の利いたことをしないのであればごく普通のリポジトリを作り、名前の最後に.g8とつけて終了です。
http://www.foundweekends.org/giter8/template.html#root+layout
activatorとの使い分けという疑問がわくかもしれませんが、activatorのアンインストールをすることで解決できます。最初からいらなかったんです。
$ bew uninstall --force typesafe-activator
7つのクラスローダー 7つの世界
Playを触っていると謎のClassNotFoundExceptionが発生することがあります。 もしかしてそれはDEVモードだけで起きてはいないでしょうか?
先日、開発中のPlayアプリケーションで、ある依存ライブラリからClassNotFoundExceptionが発生するという現象にあたりました。 そのClassNotFoundExceptionはObjectInputStream.readObjectを呼び出した時に起きるのですが、なぜこのようなことが起きるのでしょう。
それではここでPlayのソースコードにある深イイコメントを見てみましょう。
/* * We need to do a bit of classloader magic to run the Play application. * * There are seven classloaders: * * 1. buildLoader, the classloader of sbt and the Play sbt plugin. * 2. commonLoader, a classloader that persists across calls to run. * This classloader is stored inside the * PlayInternalKeys.playCommonClassloader task. This classloader will * load the classes for the H2 database if it finds them in the user's * classpath. This allows H2's in-memory database state to survive across * calls to run. * 3. delegatingLoader, a special classloader that overrides class loading * to delegate shared classes for build link to the buildLoader, and accesses * the reloader.currentApplicationClassLoader for resource loading to * make user resources available to dependency classes. * Has the commonLoader as its parent. * 4. applicationLoader, contains the application dependencies. Has the * delegatingLoader as its parent. Classes from the commonLoader and * the delegatingLoader are checked for loading first. * 5. docsLoader, the classloader for the special play-docs application * that is used to serve documentation when running in development mode. * Has the applicationLoader as its parent for Play dependencies and * delegation to the shared sbt doc link classes. * 6. playAssetsClassLoader, serves assets from all projects, prefixed as * configured. It does no caching, and doesn't need to be reloaded each * time the assets are rebuilt. * 7. reloader.currentApplicationClassLoader, contains the user classes * and resources. Has applicationLoader as its parent, where the * application dependencies are found, and which will delegate through * to the buildLoader via the delegatingLoader for the shared link. * Resources are actually loaded by the delegatingLoader, where they * are available to both the reloader and the applicationLoader. * This classloader is recreated on reload. See PlayReloader. * * Someone working on this code in the future might want to tidy things up * by splitting some of the custom logic out of the URLClassLoaders and into * their own simpler ClassLoader implementations. The curious cycle between * applicationLoader and reloader.currentApplicationClassLoader could also * use some attention. */
Playはいわゆるソースコードのホットリロード機能などを実現するために、DEVモードではなんと7つのクラスローダーが駆使されているんですね。わー、すごい。
さて、この中で重要なのは 4.applicationLoader と 7.reloader.currentApplicationClassLoader です。 applicationLoaderはライブラリをロードされているクラスローダー、一方reloader.currentApplicationClassLoaderはapplicationLoaderを親とするクラスローダーで、アプリケーションコードを読み込んでいます。
と、いうことは、Playアプリケーションのコードってreloader.currentApplicationClassLoaderにしか見えていないことになります。依存ライブラリ内のObjectInputStream.readObjectはapplicationLoaderを使っているからまあそりゃClassNotFoundExceptionが起きますね。
解決方法は、依存ライブラリがreloader.currentApplicationClassLoaderを使うようにすることです。依存ライブラリに修正入れなきゃいけないのがイケてないですがまあ仕方ない。この場合はただのObjectInputStreamの替わりに
class ObjectInputStreamWithCustomClassLoader( stream: InputStream, customClassloader: ClassLoader ) extends ObjectInputStream(stream) { override protected def resolveClass(cls: ObjectStreamClass) = { Class.forName(cls.getName, false, customClassloader) } }
のように利用するクラスローダーを選べるObjectInputStreamを作って、reloader.currentApplicationClassLoaderにあたるplay.api.Environment#classLoaderを利用します。これでClassNotFoundExceptionは出なくなります。めでたし、めでたし。
まとめ
- We need to do a bit of classloader magic to run the Play application.
- play.api.Environment#classLoader を使おう
インターフェースで副作用を分離する
この記事、わざわざgemを作ったりしていて、まあそのgem自体は便利そうだからいいと思うんですが、時刻以外の場合はどういうアプローチをしてるんでしょうか。そのたびにgemを作るんでしょうか。 そんなに悩まなくとも、オブジェクト指向の引き出しがあれば一瞬で片付く問題です。
package com.example import java.time.LocalDateTime // Clockインターフェース trait Clock { def now(): LocalDateTime } // デフォルトのClock実装 trait DefaultClock extends Clock { def now(): LocalDateTime = LocalDateTime.now() } // 時刻を取得する時にはClockインターフェースを通して取得する class SpecialContent(clock: Clock) { def enabled: Boolean = clock.now() .isAfter(LocalDateTime.of(2016, 7, 1, 0, 0, 0)) }
package com.example import java.time.LocalDateTime import org.scalatest.FunSuite // 固定された時間を返すClockインターフェースの実装 class FixedClock(fixedDateTime: LocalDateTime) extends Clock { override def now(): LocalDateTime = fixedDateTime } class SpecialContentTest extends FunSuite { test("disabled at 2016-06-30 23:59:59") { val content = new SpecialContent( new FixedClock(LocalDateTime.of(2016, 6, 30, 23, 59, 59))) assert(!content.enabled) } test("enabled at 2016-07-01 00:00:01") { val content = new SpecialContent( new FixedClock(LocalDateTime.of(2016, 7, 1, 0, 0, 1)))) assert(content.enabled) } }
java.time.Clock
というものが存在するので自分でClockを定義する必要はないんですが、そういうのがあるないの問題じゃないので。
Java/Scalaの世界とLLな世界を行ったり来たりして文化の違いに戸惑ったことがあるのですが、 こういう外部入力とかが絡んだ場合、Java/ScalaおじさんはOOPっぽくインターフェースを使って分離したりしようとするのに対して、 LLの世界だとクックパッドの記事中にも触れられているようにtimecopみたいにメソッドごと置き換えてしまうとか、個別のアプローチを取ることが多かったです。 オブジェクト指向は無駄だと主張する人が多いんですよねえ。
またScalaな世界でもOOPではなくモナドがどうという話になることがあります。 確かに参照透過であることは良いんですが別にファンクショナルなアプローチに頼らなくてもテストは書けるし、参照透過でもテストが辛いこともあるのでまあみんな仲良くしましょう。
自宅仕事環境(2016/05)
PC
MacBook Pro (Early 2013)
キーボード
PFU Happy Hacking Keyboard Professional2 Type-S 白/無刻印(英語配列)
- 出版社/メーカー: PFU
- メディア: エレクトロニクス
- クリック: 21回
- この商品を含むブログ (3件) を見る
昔はLite使ってたけどProにしたら戻れなくなった。矢印キーないのは意外と慣れる。
トラックボール
ケンジントン 【正規品・5年保証付き 】 ExpertMouse(OpticalBlack)(USB/PS2) 64325
- 出版社/メーカー: ケンジントン
- 発売日: 2004/07/20
- メディア: Personal Computers
- 購入: 5人 クリック: 200回
- この商品を含むブログ (46件) を見る
10年使ってる。ボールの触り心地が良い。
モニタ
BenQ 21.5インチワイド スタンダードモニター (Full HD/VAパネル) GW2255HM
- 出版社/メーカー: ベンキュージャパン
- 発売日: 2013/06/18
- メディア: Personal Computers
- この商品を含むブログを見る
安い
モニタアーム
グリーンハウス 液晶ディスプレイアーム 4軸 クランプ式 GH-AMC03
- 出版社/メーカー: グリーンハウス
- 発売日: 2010/07/23
- メディア: Personal Computers
- 購入: 2人 クリック: 5回
- この商品を含むブログを見る
- 出版社/メーカー: サンワサプライ
- 発売日: 2008/05/01
- メディア: Personal Computers
- クリック: 7回
- この商品を含むブログを見る
モニターアームを使った方が机がすっきりする。
椅子
オカムラ オフィスチェア バロン 可動ヘッドレスト 可動肘 座メッシュ ライムグリーン CP81CR-FDH6
- 出版社/メーカー: オカムラ (岡村製作所)
- メディア: オフィス用品
- この商品を含むブログを見る
前の職場で使っていたのが良かったなあと、似たようなものを選んだ。
ScalaMatsuri2016に行ってきた
「Steps to master the Play source code」というタイトルで発表させていただきました。 Play個別のセッションがなかったことと、どうせ関数型っぽいのが多いんだろうなと思って申し込みました。Playの初級者向けのPlayのソースコードの全体像とその変遷についての話です。
1日目は自分の出番もあったためにあまり落ち着いて見て回れませんでした。2日目のアンカンファレンスをその分楽しみました。Async Testing in ScalaTest 3.0,Scala.js Compile Pipeline,Freer Monad Extensive Effect in Scalaあたりが特に面白かったです。
Async Testing in ScalaTest 3.0はScalaTestの作者によるScalaTestにAsyncなAPIを追加したいという話でしたが、質疑応答が非常に濃く、Asyncつれーな!って感じでした。
Scala.js Compile PipelineはScala.jsコンパイラの内部的な話でしたが、いかにScala.jsがガチを思い知らされるものでした。使わないけどね!
Freer Monad Extensive Effect in Scalaはおなじみねこはる先生のセッションでしたが、なぜかScala.jsコンパイラの人がツボだったらしく終始爆笑していました。私はなるほどわからんと聞いてましたが、となりでりりろじさんが解説してくれたので助かりました。使わないと思うけどね!あとどうやらFreer Monad Extensive Effect in ScalaのウラではScalaプログラマの年収について語り合う闇のセッションがあったようですね。こわいこわい。
ScalaMatsuri全体を通しては、同時通訳のクオリティが高かったため、英語セッションも存分に楽しめる満足度の高いカンファレンスでした。1日目の昼、夜、2日目の朝、昼と食糧(うまい)の配給があり、お菓子も飲み物も無限に湧いてきて、年収が低い私もお腹いっぱいです。食糧だけでなく衣類、Tシャツやスピーカー特典のパーカーも配給されましたので、全裸で行っても楽しめたのではないかと思います。
というわけで非常に楽しいカンファレンスでした。スタッフ、スポンサーの皆様ありがとうございました。
PlayのWebCommandsの使い方
Playのドキュメントを見るときに、公式サイトに行かなくても手元で http://localhost:9000/@documentation というURLにアクセスすればドキュメントサイトが見えるということはご存知でしょうか?
これにはWebCommandsという仕組みで動いていて、ドキュメントのほかにもEvolutionsの実行ページの表示などにも使われています。 普通のアプリケーションを作る分には必要ないですが、開発用のライブラリにWeb UIをつけたいときなどは便利です。
例えば、/@hello, /@hello/:name というパスにアクセスすると反応するWebCommandは次のような具合です。
package controllers import javax.inject.{Provider, Singleton, Inject} import play.api._ import play.api.inject.{Binding, Module} import play.api.mvc._ import play.core.{HandleWebCommandSupport, WebCommands} // 処理本体 class HelloWebCommandHandler extends HandleWebCommandSupport { def handleWebCommand(request: play.api.mvc.RequestHeader, buildLink: play.core.BuildLink, path: java.io.File): Option[play.api.mvc.Result] = { val pathPattern = """/@hello/([a-zA-Z0-9_]+)""".r request.path match { case """/@hello""" => Some(Results.Ok("Hello!")) case pathPattern(name) => Some(Results.Ok("Hello, " + name + "!")) case _ => None } } } // 以下DIのためのお決まりのコード @Singleton class HelloWebCommand @Inject() (webCommand: WebCommands) { webCommand.addHandler(new HelloWebCommandHandler()) } class HelloWebCommandModule extends Module { override def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]] = { Seq(bind[HelloWebCommand].toSelf.eagerly) } }
DIのbindのためのコードが多くて目がやられますが、HelloWebCommandHandlerというのが処理本体です。 簡単ですね。パスにマッチしたらなにか結果をOptionにくるんで返せばいいわけです。 あとはモジュールをapplication.confで登録すれば動きます。
play.modules.enabled += "controllers.HelloWebCommandModule"
以上、scalamatsuri前の小ネタでした。
アプリケーションに合ったExecutionContextを使う
scalaではFutureなどの裏側ではExecutionContextが動いています。ということはExecutionContextの使い方がいまいちだとFuture周りで問題が起きることになります。
よく起きる問題の1つとして、標準のExecutionContextがいまいちだった、というのがあると思います。 標準のExecutionContextはダメというか、適さないパターンがあるのですが、あまりよく考慮されずに使われてしまう印象があります。 まあ悪いのはコンパイラの親切すぎるエラーメッセージでしょう。
scala> import scala.concurrent.Future import scala.concurrent.Future scala> Future { 1 + 1 } <console>:9: error: Cannot find an implicit ExecutionContext. You might pass an (implicit ec: ExecutionContext) parameter to your method or import scala.concurrent.ExecutionContext.Implicits.global. Future { 1 + 1 } ^ scala> import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global scala> Future { 1 + 1 } res2: scala.concurrent.Future[Int] = scala.concurrent.impl.Promise$DefaultPromise@2bbaf4f0 scala> res2.foreach(println) 2
このようにScalaでFutureを使おうとすると、ExecutionContextがないからimport scala.concurrent.ExecutionContext.Implicits.globalをどうとか言われるので、とりあえず使っとくかーみたいな気分になっちゃいます。
標準のExecutionContextの特徴
まず、標準のExecutionContextはスレッド数がそんなに多くありません。2.11では最大でもコア数分のスレッドしか立ち上がりません。 それではデッドロックが起きやすいので2.12ではコア数+256までとなっているようですが、用途によってはもっと増やしたいですね。
それから、標準のExecutionContextはForkJoinPoolを使って実装されています。ForkJoinPoolは
It is designed for work that can be broken into smaller pieces recursively
https://docs.oracle.com/javase/tutorial/essential/concurrency/forkjoin.html
とのことで、parallel collectionとかには向いてそうだけどWebアプリケーションとかには向かなそうですね(ダメってこともないだろうけど)。 みなさんの用途には合ってるでしょうか?
アプリケーションに合ったExecutionContextを使う
標準のExecutionContextをやみくもに使うのはやめて、アプリケーションに合ったExecutionContextを使いましょう。 作り方はExecutionContext.fromExecutor, ExecutionContext.fromExecutorServiceを使えばjava.util.concurrentで作ったExecutor/ExecutorServiceを元にExecutionContextを作る、で大抵事足りる気がします。
Futureを使う時にJavaのExecutorServiceを利用する - CLOVER
フレームワークを使っている場合はフレームワークが独自のスレッドプールを用意していることがあります。
Playの場合はakkaのスレッドプールを利用していて、import play.api.libs.concurrent.Execution.Implicits._
することで有効になります。
それから、ドキュメントのスレッドプールについての箇所は必読です。
ライブラリのコードに標準のExecutionContextを含めない
Futureなどを用いたライブラリを作るときは、標準のExecutionContextをハードコードしないように注意してください。 ExecutionContextはメソッドの引数経由で渡すなど、なんらかの形で利用者が調整できるようにしましょう。
まとめ
ScalaではFutureなどのおかげで気軽に非同期処理などが利用されますが、その裏にはスレッドプールであるExecutionContextがいます。 しかし、ExecutionContextはimplicitパラメータとして暗黙のうちに引き回されるため、よくも悪くもあまり意識されません。困ったもんですね。
ExecutionContextついてはその実装を見てみることも参考になりますが、やはりJavaのスレッド周りの知識が不可欠です。 Scalaを始めたけれどJavaはよく分からないという人はjava.util.concurrent(便利)の使い方を学んでおくと雰囲気がつかめるかなと思いました。