for式の中のEitherでifを使いたい

scala 2.12ではJava8対応の変更が多く、派手な変更は多くはありませんでした。そんな中でEitherがright-biasedになったのは割とキャッチーなのではないでしょうか。Eitherには今までflatMapなどのメソッドがなかったので、for式の中で使うときなど、いったんRightProjectionに変換する必要があったのですが、それが必要なくなりました。

for {
  x <- Right(3).right
} yield x

これが

for {
  x <- Right(3)
} yield x

こう書けるようになりました。

これは便利ですね。まあrightが取れるだけなんですが、私もよく .right つけろよとコンパイラに怒られていたので嬉しいです。

さて、これでscalaのEitherが実用的になったという声も聞こえるのですが、実際 .right が不要になったから実用的かというと疑問で、どちらかというとscalaのEitherで困るのはfor式の中でifが使えないことだと思います。私はwithFilter問題と勝手に呼んでいてRightProjectionにwithFilterがないせいなんですが、この問題は2.12.xでもまだ健在です。

scala> for {
     |   x <- Right(3)
     |   if x % 2 == 1
     |   y = x + 1
     | } yield y
<console>:13: error: value withFilter is not a member of scala.util.Right[Nothing,Int]
         x <- Right(3)
                   ^

ほら、if使えないでしょ。これは困りますね。そこでEitherに限った方法ではないんですが、ちょっとヘルパーを定義します。

def when[E](p: Boolean)(e: => E): Either[E, Unit] =
  if (p) Right(()) else Left(e)

このwhenはpの条件を満たせばRightを返すのでそれ以下のfor式も実行されます。満たさなければLeftを返してエラーにします。

scala> val result = for {
     |   x <- Right(3)
     |   _ <- when(x % 2 == 1)("error")
     |   y = x + 1
     | } yield y
result: scala.util.Either[String,Int] = Right(4)

scala> val result = for {
     |   x <- Right(3)
     |   _ <- when(x % 2 == 0)("error")
     |   y = x + 1
     | } yield y
result: scala.util.Either[String,Int] = Left(error)

これでEitherでもifっぽいことをできるようになり、めでたしです。

追記

@gakuzzzzさんにEither.condの存在を教えられた。

def when[E](p: Boolean)(e: => E): Either[E, Unit] =
  Either.cond(p, (), e)

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

f:id:tototoshi:20161009234716p:plain

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

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

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

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

giter8 & sbt new

最近は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を触っていると謎の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に行ってきた

「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シャツやスピーカー特典のパーカーも配給されましたので、全裸で行っても楽しめたのではないかと思います。

というわけで非常に楽しいカンファレンスでした。スタッフ、スポンサーの皆様ありがとうございました。