Subscribed unsubscribe Subscribe Subscribe

Scala関西 Summit 2016に参加しました

Scala

f:id:tototoshi:20161009234716p:plain

sbt再入門ということで、sbtの主にKeyとScopeについてデモを中心に発表させていただきました。 デモを中心にするのは自分としては挑戦のつもりだったけれど、やっぱり難しくて心残りなところもありました。 とはいえ発表中に実際にsbtを触りつつ話を聞いてくれてる方が多かったのは嬉しかったです。 スライドの内容は全てsbtのドキュメントにあるので特に公開する予定はありません。かわりにドキュメントを読んでもらえればと思います。

イベントはとても楽しかったのですが、全体の空気は去年よりトーンが落ちたというか、淘汰されてしまった印象がありました。切ない。関西勢の発表が少なかったからでしょうか。そう考えるとScala盛り上がってる感出すにはとりあえずCFP出すことって大事ですね。自分も次のScalaMatsuriはどうしようか迷っていたんですがとりあえず出してみようと思います。ElixirとFregeどっちが良いでしょうか。

きの子さんをはじめ、スタッフの方々、ありがとうございました。きの子さんのテンションと声の通りを前にするとやや自信を失くします。私は東で生きていきます。

画像は偶然見つけたパチンコ屋さんです。派手ですね。

giter8 & sbt new

Scala sbt

最近はactivator newを使っている人が多いんだと思いますが、5年くらい前?はgiter8というツールが使われていました。 giter8は一時期Typesafe Stackにも含まれるほどだったんですが、なぜか見捨てられてしまいます。 その後皆さんご存知の通りactivator newに取って代わられ、開発も下火になり、知る人ぞ知るみたいな存在になってしまいましたが、少し前にpamflet、conscriptとともにfoundweekendsに拾われ、メンテナンスが継続されていくことになりました。

github.com

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 Scala

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 を使おう

インターフェースで副作用を分離する

プログラミング

techlife.cookpad.com

この記事、わざわざ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)

f:id:tototoshi:20160523214129j:plain

PC

MacBook Pro (Early 2013)

主にScalaコンパイルするのに使用する。

キーボード

昔はLite使ってたけどProにしたら戻れなくなった。矢印キーないのは意外と慣れる。

トラックボール

ケンジントン 【正規品・5年保証付き 】 ExpertMouse(OpticalBlack)(USB/PS2) 64325

ケンジントン 【正規品・5年保証付き 】 ExpertMouse(OpticalBlack)(USB/PS2) 64325

10年使ってる。ボールの触り心地が良い。

モニタ

BenQ 21.5インチワイド スタンダードモニター (Full HD/VAパネル) GW2255HM

BenQ 21.5インチワイド スタンダードモニター (Full HD/VAパネル) GW2255HM

安い

モニタアーム

グリーンハウス 液晶ディスプレイアーム 4軸 クランプ式 GH-AMC03

グリーンハウス 液晶ディスプレイアーム 4軸 クランプ式 GH-AMC03

サンワサプライ ノートパソコンアーム CR-LANPC1

サンワサプライ ノートパソコンアーム CR-LANPC1

モニターアームを使った方が机がすっきりする。

椅子

前の職場で使っていたのが良かったなあと、似たようなものを選んだ。

ScalaMatsuri2016に行ってきた

Scala

「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の使い方

Scala Play

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前の小ネタでした。