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年に初開催。今年で二回目です。
イベントページはこちら↓↓↓
発表内容とか
タイトルは「今から始める 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 も単純な値の呼び出しに置き換えられるという話が書かれているっぽいです。
せっかくなので自分の環境でも試してみました。 実行環境は以下の通り。
で、実行結果が以下の通り。baseline
が implicitly
で、measure
が imp
の結果です。
[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 を使うと良いんじゃないでしょうか?
Typelevel.scala Projects Stickers が欲しい
この記事は ADVENTAR 版 Scala Advent Calendar 2016 1日目の記事です。 明日もあたしです。埋まらないのは悔しいので極力埋め作業しようと思いますが、全然ウェルカムなのでどうぞ。
初日なので(?)一切 Scala のソースコードが出てこない記事です。
Typelevel.scala Projects Stickers って?
It fits great isn't it? pic.twitter.com/A3988jVHBB
— Julien Truffaut (@JulienTruffaut) 2016年11月21日
Had to take off my Scala sticker to make room for more Typelevel stickers ;-) pic.twitter.com/Hdn7bVjQF9
— Rob Norris (@tpolecat) 2016年10月15日
@typelevel stickers aplenty at @ScalaByTheBay pic.twitter.com/OqiGBin6fr
— Adelbert Chang (@adelbertchang) 2016年11月12日
New Monocle and Circe stickers are awesome :-) /cc @JulienTruffaut @travisbrown @typelevel #Scala pic.twitter.com/zNpTMe36fW
— Miles Sabin (@milessabin) 2016年11月28日
.@http4s stickers arrived! pic.twitter.com/UXo67GuEQn
— Ross A. Baker (@rossabaker) 2016年11月30日
とても cooool ですね。欲しくなりましたね?
注文したい
Typelevel のトップページ -> About から stickermule ってページに飛べます。 www.stickermule.com
好きなステッカーをポチポチして右側の checkout で住所入力画面に飛びます。 住所の入力方法は↓あたりを参考にするといいと思います。
注文ボタンを押すと、入力した住所の確認モーダルが表示されるので改めて正しく入力できてるか確認しましょう。
ってことで、一通り全種類注文してみました。 予定だと16日に届くらしいので楽しみですね。到着したらまた記事書けますし(ぇ
まとめ
この前に書いた記事が昨年度末の 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 }
で、今回はとりあえずrewrite
とtransform
の使い方(?)だけざっくり。
対象のデータ型は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)さんが開発してるライブラリです。
使い方は簡単。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
の紹介でした。
素敵なマクロライフを〜。