独学大学情報学部

ただのノート。主にプログラミング。

Scala関西 Summit 2016 で Lens/Prism について発表してきた

日付変わってしまいましたが、Qiita 版 Scala Advent Calendar 2016 の12日目の記事です。

前日: grimrose さんの ScalikeJDBCを使ってAmazon Athenaへアクセスしてみた
翌日: jwhaco さんの いい感じの sbt パーサを作る

blog 記事を書くまでがイベントです。 とても遅くなってしまいましたが、10/8 に開催された Scala関西 Summit 2016 のレポートです。

Scala関西 Summit 2016 とは

関西最大級の Scala カンファレンスで、2015年に初開催。今年で二回目です。
イベントページはこちら↓↓↓

summit.scala-kansai.org

発表内容とか

タイトルは「今から始める Lens/Prism」です。
資料は SlideShare に上げてありますので見直したい方はどうぞ。

www.slideshare.net

以下、思い出とか発表の背景とか。

資料は reveal.js 使って作成しました。 当日はブラウザで index.html を開いて発表しようと考えていたんですが試しにプロジェクターと接続した瞬間レイアウト完全崩壊。 本番では発表15分前になんとなく PDF にして SlideShare に上げておいた奴を使って事なきを得たので、事前の準備大事ですよ!!!という教訓...*1

今回の発表は 46, 47 ページを境にざっくり前半と後半部分に分かれています。 前半部分は Scala で Lens/Prism を使う際のモチベに始まり、getter/setter を抽象的に考えていって Lens を導入。 法則やら合成やら Prism やら Optics Hierarchie やらをざっとなぞって完。 そして後半に「今から始める〜」とか入門編みたいなタイトルにしておきながら基礎理論的な内容として van Laarhoven Lens の話をしました。 後半に関しては正直なところ入門的な話の後にする内容ではないとは思っていたのですが、 それ以上に自分が理解した時のテンション&概念の面白さを伝えたかったのと、 途中に出てくる traverse 関数から map と foldMap を導出する話が Scala 関数デザイン&プログラミングの 12章で語られていて、 書籍からの一歩進んだ応用例みたいな位置付けとして面白そうだなと思い盛り込む事にしました。 振り返ってみると、結果的に内容に緩急もついて良かったんじゃないかなと思ってます。

他に時間やら理解度やらの都合で話せなかった Lens の歴史や別な実装である Store Commonad Lens の話などなど、 色々と面白くて興味深い内容もまだまだたくさんあるので機会があれば関連内容でまた発表したいですね。 例えば、タイムテーブルも公開され始めた 2/25, 2/26 に東京国際交流館で開催される ScalaMatsuri 2017 の二日目、アンカンファレンスとかいいかもしれませんね!!!!!

本当に発表するかはさておき、とても面白い分野(?)なので来年も引き続きお金のかからない Lens 沼に浸っていられそうな気がしています。

まとめ

関西 Scala コミュニティの盛り上がりをすごく感じましたし、とても楽しく充実した1日を過ごせました。 スタッフの皆さん本当にお疲れさまでした。ありがとうございました。次回の開催も心より楽しみにしております!

Special Thanks

開催前日に @kawachi さんと @lyrical_logical さんより資料や発表の構成についてアドバイスを頂きました。特に @lyrical_logical さんには文章や細かい言い回しから理論的な監修に至るまで長時間おつきあい頂きました。 お二人のアドバイスがなければあの資料は完成しませんでした。本当にありがとうございました。

*1:今回は資料を発表直前まで弄っててその辺怠ってた...今までは何もなかった慢心...

Imp と implicitly

かなり遅刻してしまいましたが、Adventar 版 Scala アドベントカレンダーの2日目です。

前日: Typelevel.scala Projects Stickers が欲しい
翌日: so_zaneli さんの finagle-toggleでデプロイとリリースを分離する

今回は implicitly と Imp ってライブラリ話を書こうと思います。

implicitly is slow

implicitly はご存知ですね。Scala 標準ライブラリの Predef に以下のように定義されています。※ 2.11.8 時点。

@inline def implicitly[T](implicit e: T) = e

要するに型パラメータを明示的に指定してスコープ内に定義されている指定した型の暗黙的な値を取得する為のメソッドなわけですが、 こいつは大抵以下のように使われて(理屈上では?) implicitly の処理分遅いよねって話があります。

def fast[A](implicit ev: Monoid[A]): A =
  ev.empty

def slower[A: Monoid]: A =
  implicitly[Monoid[A]].empty

def alsoSlower[A: Monoid]: A =
  Monoid[A].empty // without imp

※ cats, spire の作者 Erik Osheim さんの Scala World 2015 での発表資料の84枚目より。

alsoSlower 内の Monoid#apply も資料内に定義は書いてありませんが恐らく

def apply[A](implicit A: Monoid[A]): Monoid[A] = A

あたりで implicitly と同様な実装になってることでしょう。 これも結果的には alsoSlower 内で implicitly と同様の apply 処理分遅くなります。 そして、こういった無駄な処理を削りたい!って思った時に同氏作の Imp ってライブラリを使うと良いよって話です。

Imp とは

どんなライブラリかと言うと README に書いてある下記一行で全てです。

It provides a zero-cost macro to summon implicit values.

実装もとてもシンプルでコメントや import を無視すると実装自体は実質二行。

def imp[Ev](implicit ev: Ev): Ev = macro summon[Ev]

def summon[Ev](c: Context)(ev: c.Expr[Ev]): c.Expr[Ev] = ev

要するに macro を使って compile 時に暗黙的な値をそのまま置き換えて(召喚して)しまうというわけです。 使い方も README に書いてある通りとても簡単。

念のため typer phase で確認してみましょう。 適当に以下のようなコードを書いて、Show#apply の実装を書き換えて compile してみます。

class Foo()

trait Show[A] {
  def show: String
}

object Show {

  def apply[A: Show]: Show[A] = implicitly[Show[A]]
  //   or
  def apply[A: Show]: Show[A] = imp.imp[Show[A]]

  implicit val showFoo: Show[Foo] = new Show[Foo] {
    def show = "foooooooooooooo"
  }
}
  • implicitly
  object Show extends scala.AnyRef {
    // ...
    def apply[A](implicit evidence$1: Show[A]): Show[A] = scala.this.Predef.implicitly[Show[A]](evidence$1);
  • imp
 object Show extends scala.AnyRef {
    // ...
    def apply[A](implicit evidence$1: Show[A]): Show[A] = evidence$1;

implicitly と用いた場合は apply の引数が implicitly メソッドに渡されてますが、imp を用いた場合は apply の引数がそのまま返されています。 これで implicitly の呼び出し分早くなるよ、やったね!!

implicitly も実質ゼロコスト?

喜びも束の間。近年の Hotspot の最適化の前には Imp 不要では?という話が。 README の Known Issues ってところに書いてある一文を引用します。

Dmitry Petrashko has argued persuasively that modern Hotspot optimizations mean that Imp is unnecessary. See the imp-bench repository for more information on his benchmarks.

Dmitry Petrashko*1 さんの jmh を使った benchmark

によると、Hotspot の最適化でもって implicitly も単純な値の呼び出しに置き換えられるという話が書かれているっぽいです。

せっかくなので自分の環境でも試してみました。 実行環境は以下の通り。

  • OS: OS X Yosemite 10.10.5
  • CPU: 3 GHz Intel Core i7
  • メモリ: 16 GB 1600 MHz DDR3

で、実行結果が以下の通り。baselineimplicitly で、measureimp の結果です。

[info] Benchmark                                 Mode  Cnt  Score   Error  Units
[info] addable.ImplVsImplicitlyAddable.baseline  avgt   30  2.444 ± 0.070  ns/op
[info] addable.ImplVsImplicitlyAddable.explicit  avgt   30  2.649 ± 0.215  ns/op
[info] addable.ImplVsImplicitlyAddable.imply     avgt   30  2.554 ± 0.135  ns/op
[info] bench.ImplVsImplicitly.baseline           avgt   30  3.025 ± 0.050  ns/op
[info] bench.ImplVsImplicitly.measure            avgt   30  2.905 ± 0.020  ns/op

うぅm、README の Result とは微妙に差のある結果ですね。 Error 率も差があまりないようですし、間違ってはなさそうな気もしますが...。 この最適化の方の話については別な日に詳しく追うことにします... *2

まとめ

Hotspot の最適化も優秀っぽいけど、最適化に身を委ねたくない人は Imp を使うと良いんじゃないでしょうか?

*1:EPFL Scala Team の一人。記事公開時点で dotty の commit 数2位。

*2:記事を書くとは言っていない?

Typelevel.scala Projects Stickers が欲しい

この記事は ADVENTAR 版 Scala Advent Calendar 2016 1日目の記事です。 明日もあたしです。埋まらないのは悔しいので極力埋め作業しようと思いますが、全然ウェルカムなのでどうぞ。

初日なので(?)一切 Scalaソースコードが出てこない記事です。

Typelevel.scala Projects Stickers って?

とても cooool ですね。欲しくなりましたね?

注文したい

Typelevel のトップページ -> About から stickermule ってページに飛べます。 www.stickermule.com

好きなステッカーをポチポチして右側の checkout で住所入力画面に飛びます。 住所の入力方法は↓あたりを参考にするといいと思います。

注文ボタンを押すと、入力した住所の確認モーダルが表示されるので改めて正しく入力できてるか確認しましょう。

ってことで、一通り全種類注文してみました。 予定だと16日に届くらしいので楽しみですね。到着したらまた記事書けますし(ぇ

f:id:Aoino:20161201221148p:plain

まとめ

この前に書いた記事が昨年度末の Advent Calendar なので、ほぼ一年間自分のブログでは何も書いてないことに気づいた。

Platedとは何なのか?

この記事はadventar版Scalaアドベントカレンダーの21日目です。
担当は@aoiroaoinoです。17日ズサーしてる場合じゃなかった。とっても遅刻しちゃってすみません。。

まえがき

いつも通りMonocleの話です。事の発端はこのissueとPRでした。

https://github.com/julien-truffaut/Monocle/issues/288 https://github.com/julien-truffaut/Monocle/pull/289

全く知らなかったのだけど、追加されたexampleやらtestやら眺めてたら便利そうだったので。

Platedのインスタンス

まず、Plated自体は型クラスで定義は以下のとおり。

abstract class Plated[A] extends Serializable { self =>
  def plate: Traversal[A, A]
}

Plated自体はTraversal[A, A]なplateを一つ要求してるだけです。本当にこれだけ。 で、Traversalは以下のように定義されています。

type Traversal[S, A] = PTraversal[S, S, A, A]
abstract class PTraversal[S, T, A, B] extends Serializable { self =>

  /**
   * modify polymorphically the target of a [[PTraversal]] with an Applicative function
   * all traversal methods are written in terms of modifyF
   */
  def modifyF[F[_]: Applicative](f: A => F[B])(s: S): F[T]

  ...
}

つまり、Platedのインスタンスが要求してるのはdef modifyF[F[_]: Applicative](f: A => F[B])(s: S): F[T]という メソッドを定義しろって言ってるだけです。

で、Monocle(v1.2.0 現在)で定義されてるインスタンスは以下の8つです。

要するにTraversalを定義できればいいので、例えば自前で定義した型のインスタンスを定義することもできます。 例えばexampleにあるJsonの例を参考に、json4sのJsonASTに対して定義してみるとこんな感じ。*1

implicit def jValuePlated: Plated[JValue] = new Plated[JValue] {
  val plate: Traversal[JValue, JValue] = new Traversal[JValue, JValue] {
    def modifyF[F[_]: Applicative](f: JValue => F[JValue])(x: JValue): F[JValue] =
      x match {
        case j@(JString(_) | JBool(_) | JDouble(_) | JDecimal(_) | JInt(_)| JNull | JNothing) => Applicative[F].point(j)
        case JArray(a) => a.traverse(f).map(JArray)
        case JObject(o) => o.toMap.traverse(f).map(_.toList).map(JObject.apply)
      }
  }
}

で、インスタンスの定義方法をだらだら書きましたが、んじゃこんなの定義して何が嬉しいのかって話です。

Platedの使い方

Plated自体はインスタンスになってるデータ型に対して以下の関数を提供します。 ※見やすさの為にコメントやら実装やらを消しました。元のコードはこちらを参照してください。

trait PlatedFunctions {

  def children[A: Plated](a: A): List[A]

  def universe[A: Plated](a: A): Stream[A]

  def rewrite[A: Plated](f: A => Option[A])(a: A): A

  def rewriteOf[A](l: Setter[A, A])(f: A => Option[A])(a: A): A

  def transform[A: Plated](f: A => A)(a: A): A

  def transformOf[A](l: Setter[A, A])(f: A => A)(a: A): A
}

で、今回はとりあえずrewritetransformの使い方(?)だけざっくり。 対象のデータ型はJsonが分かりやすそうなので、上で定義したjson4sのやつを使います。 ※ちゃんと動くサンプルコードはこちら

操作する対象のjsonはこんな感じ。

val json: JValue = JObject(
  "name"      -> JString("aoino"),
  "age"       -> JInt(25),
  "favorites" -> JArray(List(
    JString("scala"),
    JString("haskell")
  )),
  "address"   -> JObject(List(
    "country"  -> JString("japan"),
    "city"     -> JString("tokyo"),
    "postcode" -> JInt(12345)
  ))
)

rewrite

例えばJValueの場合はdef rewrite[JValue](f: JValue => Option[JValue])(a: JValue): JValueとなりますね。 第二引数で受け取ったa: JValueの要素に対してfを適用していき、Someだったら再度適用、Noneだったら次の要素というのを繰り返し、それを全要素に対して行います。畳み込みの様な振る舞いをします。

it("rewrite: Apply toUpperCase to all JString") {
  Plated.rewrite[JValue] {
    case JString(s) =>
      val x = s.toUpperCase
      if (s != x) Some(JString(x)) else None
    case _ => None
  }(json) shouldEqual JObject(
    "name"      -> JString("AOINO"),
    "age"       -> JInt(25),
    "favorites" -> JArray(List(
      JString("SCALA"),
      JString("HASKELL")
    )),
    "address"   -> JObject(List(
      "country"  -> JString("JAPAN"),
      "city"     -> JString("TOKYO"),
      "postcode" -> JInt(12345)
    ))
  )
}

transform

def transform[JValue](f: JValue => JValue)(a: JValue): JValue こいつも同様に全要素を走査するんですが、rewriteと違って、fを適用した結果に関わらず次の要素に進みます。 おなじみのmapの様な振る舞いをします。

it("transform: JInt to JDouble") {
  Plated.transform[JValue] {
    case JInt(i) => JDouble(i.toDouble)
    case x       => x
  }(json) shouldEqual JObject(
    "name"      -> JString("aoino"),
    "age"       -> JDouble(25.0),
    "favorites" -> JArray(List(
      JString("scala"),
      JString("haskell")
    )),
    "address"   -> JObject(List(
      "country"  -> JString("japan"),
      "city"     -> JString("tokyo"),
      "postcode" -> JDouble(12345.0)
    ))
  )
}

で、これを知って一層某MonadicJValueとかいうclassに対して何がもなでぃっくじゃーみたいな気分になれるわけです。

結局Platedは何なのか?どこから来たのか?

本当はここをメインに書きたかったんですけど、掲載日過ぎた今日現在でもあんまりちゃんと理解できてないので調べた内容だけ。

issueにも貼られていたけど、Monocleの実装の元になったControl.Lens.Platedで、概要を読んでみると、どうやら Scrap Your Boilerplate の話で、Neil Mitchell さんの Uniplateが元になってるっぽい。元の論文は同氏の Uniform Boilerplate and List Processing で、どうやら2007年開催のHaskell Workshop (?)で発表されたものらしい。はい、ここまでです。

まとめ

見つけた肝心の論文に関しては読み始めたものの、相も変わらずぐうの音も出ない状態なので、皆さんなんでそんなにカジュアルにラノベ感覚で論文読めるんですかっ???って気分になったので、引き続き頑張りますとだけ...。まさかLens楽しくて追っかけてたらSYBに至るとは思ってなかったので、しばらくはSYB関連調べて浸ってみるのも良さそうってのが感想です。元々気にはなってたしね。あと来年はHaskellと英語頑張るのが良さそうですね。良いお年を。(早

macro-compatを使ってmacroの互換性を簡単に管理する

この記事はadventar版Scalaアドベントカレンダーの17日目です。

21日のみ書く予定でしたが、17日空いてるのも寂しいので ズサーc⌒っ゚Д゚)っ しました。 あ、Monocle v1.2.0 がリリースされましたよ。めでたいですね!!

さて、本日はそんなMonocleでも使ってる(?)Scalaのマクロのお話です。個人的にはそもそもあんまりマクロ使わないですし、使ったとしても2.11系のみ対応だったりで、2.10系との互換性を意識したことがなかったんですが、Scala複数のメジャーバージョンを跨ぐライブラリともなると話は別なようです。

Monocle(執筆時 v1.2.0)ではLensやPrism、Isoなどを生成するようなマクロがあります。これまでは、2.10系と2.11系のマクロAPIの差はscala-2.10, scala-2.11ディレクトリ以下にそれぞれMacrosCompatibilityというtraitを作って、そこで吸収していました。

Monocle/MacrosCompatibility.scala at v1.2.0-M1 · julien-truffaut/Monocle · GitHub

Monocle/MacrosCompatibility.scala at v1.2.0-M1 · julien-truffaut/Monocle · GitHub

最近はsbt側で上記ディレクトリの管理もやってくれるようになりましたが、依然コードの方は自前で書かなければなりません。

そこで本日ご紹介するのが macro-compat という、Miles(@milessabin)さんが開発してるライブラリです。

github.com

使い方は簡単。2.11.xのAPIを使って普通にマクロを書いて、その実装を書いたclassに@bundleをつけると勝手に2.10.x系向けのコードも生成してくれます。

やってることは単純で、2.11.xのAPIに対応する2.10.x向け変換メソッドをimplicitとか使ってゴリゴリと定義してあるBundleトレイトをマクロアノテーションを使って、該当のクラスにmix-inしてるだけです。コード例書くのも面倒なので、ちょっと前にMonocleに入れた際のプルリクURL貼っておくので、許して><

Use macro-compat by aoiroaoino · Pull Request #276 · julien-truffaut/Monocle · GitHub

大変便利なmacro-compatですが、まだまだ開発途中の様子で、しばしば欲しい変換メソッドが定義されていなかったりします。 仮にもし定義されてなかった場合は、チャンスなので容赦なく(?)Pull Reqしましょう!

以上、とってもあっさりですがmacro-compatの紹介でした。
素敵なマクロライフを〜。