MonocleとScalazにcontributeした話
わざわざ丁寧に書く内容ではないよなーとか思ってたので書くつもりは全くなかったけど、色々思うところがあったのでブログのネタにした。
ちょっと前からLens&Prism関連の話に興味を持ってて、ScalaでLensを提供するMonocleってライブラリをよく触って遊んでた。 だいぶ慣れてきたので、自社のプロダクトコードに導入しようと試してみたり*1、 argonautを参考にこんなの作り始めたりしたけど、使ってると案の定不満とか足らない機能とか見えてくるわけです。 で、これまでのfix typoしてきた経験(?)を生かして、少しだけcontributeしたってだけの話です。
Foldにlengthメソッド追加できるんじゃね?
monocle.Fold
の中身眺めてたらfoldMap
ってメソッド名見えるし、なんならscalaz.Foldable
からFoldインスタンス作れるし、本家Haskellのlensパッケージの方にもlength
メソッドが存在するくらいだから、追加できるやろと思ってやってみたら出来たのでPR出してマージされた。
https://github.com/julien-truffaut/Monocle/pull/236
Either3のOpticsがなかった
Monocleのpackage構成って、Scalazと似たような構成になっていて*2、monocle/std
ディレクトリ以下に各種データ構造に対するインスタンスが定義されてます。その中身をScalazの方のscalaz/std
と比べてみると、Monocle側に存在しない奴が幾つか見つかるわけですね。んで、その中でもEither3
が簡単そうだったので、既存のmonocle.EitherOptics
を参考にして追加のPR出したらマージされた。
https://github.com/julien-truffaut/Monocle/pull/242
ちなみにEither3
って何だよって思った方は定義をどうぞ。名前通りの中身ですね。
EphemeralStreamのOpticsがなかった
もう一つ見つけた中で簡単そうだったのがscalaz.EphemeralStream
。
どういうものかって書こうとしたら、既に2年以上も前に書かれてました。*3
ほとんど中身は変わってないっぽい(?)ので、詳しくはこちらで。
Scalaz の EphemeralStream - scalaとか・・・
で、このscalaz.EphemeralStream
にたいしてOptics
が定義できそうだったので挑戦してたんだけど、
monocle.function.FilterIndex
型クラスのインスタンス定義にFilterIndex#traverseFilterIndex
メソッドを使おうとして、
scalaz.EphemeralStream#zipWithIndex
が無かった事に気づいた。
どう定義するのが一番綺麗か悩んだけど、PR出してマージされたので良かった。
https://github.com/scalaz/scalaz/pull/998
ただ、Monocleの方はまだ出してないです。 特に技術的難題にぶつかった訳ではないんですけど、こんなissueがあって、
Experiment replacing Scalaz by Cats dependency · Issue #235 · julien-truffaut/Monocle · GitHub
Gitterでも言及してるの見た事あるし*4、Julien Truffaut氏的にはScalazからCatsに移行したい様子っぽいので、Scalazにしかないようなデータ構造やらに依存するようなPR出しまくってえぇんか?的な思いに至って以降、忙しさを言い訳にちょっと放置中です。
ただ、内部的にはScalazべったりでそんなに簡単にはいかないだろうし、Scalazにしかないようなデータ構造に対するOpticsは別途モジュール切り出してサポートするって言ってるけど、以降のサポートとかも含めてちょっと難しいんじゃない?ってのが正直なところです。
それ、unused import じゃね?
ふとPR一覧眺めてると、Julien Truffaut氏がpackage構成変えるPR出してて、なんとなく-Ywarn-unused-import
つけてcompileしてみたら、unused import
のエラー*5が出てきたのでコメントしてみた。
fix #238 replace package object by all object by julien-truffaut · Pull Request #243 · julien-truffaut/Monocle · GitHub
結果的には
package object std extends StdInstances
って定義されてるpackage.scala
ファイルの消し忘れっぽかったんだけど、なんか深夜に完全にテンパって仕様上ありえないコメントとかしちゃって辛かった(←
ただ、-Ywarn-unused-import
オプション追加に関しては前向きっぽいので、追加PR出してみるといいんじゃないですかね?チャンスです。
^|->> や ^<-> って書くのが嫌になってきた
見出しの記号はそれぞれcomposeTraversal
とcomposeIso
ってメソッドのエイリアスで、使う場面というか使い方は
Monocleのexampleや
「Scalaに存在演算子を求めるのは間違っているだろうか」をLens/Prismで解いてみる - 独学大学情報学部
とか見ればなんと無くわかるんじゃないかと思います。 とりあえず、compose系メソッド名と記号メソッド名の対応は覚えたんだけど、記号メソッド入力するときにほぼ右手のみでしかも4-5文字ってめちゃくちゃキーボード打ちにくくて不便なので、何か共通の記号一文字でできないかと思ってやってみたのが、これ。
orz · aoiroaoino/Monocle@5cf5d9a · GitHub
以前のv0.x系の頃には一律compose
ってメソッド名だったのだけど、こんな感じでoverloadするとうまく動作しない問題があった。それを暗黙の変換かますと回避できたりしないかなーってなんと無く思ってやってみたけど、そんなことはなかったし、詳しく追うのも疲れたのでお蔵入りコードとなりました。南無三。この問題、回避策をご存知の方いれば教えて頂きたく。
まとめ
ここ三週間くらいプライベートではこんな感じの事やってた。 英語は適当過ぎるの良くないけど、それなりに頑張るとそれなりに通じるっていう知見を得たので、議論できる程度まで上げたいですね、英語力。
追記 2015-09-16
アラカルト形式importはscalazが最初ではない模様。
@AoiroAoino アラカルト形式のimport、どこからそう呼ぶのか微妙なところもあるけど、これ https://t.co/QDSbYzsB0K をそうだとするなら、7年以上前からあるしscalazが最初ではないかも(そしてscala-timeが最初かどうかも知らない)
— Kenji Yoshida (@xuwei_k) September 15, 2015
追記 2015-09-17
cats化難しいんじゃないの?とかって内容の記事書いて次の日にcats化のブランチがremoteにpushされたし。 うぅむ、PR出てくるのも時間の問題っぽい...。
「Scalaに存在演算子を求めるのは間違っているだろうか」をLens/Prismで解いてみる
元の記事はこちらです。
Scalaに存在演算子を求めるのは間違っているだろうか - だいたいよくわからないブログ
TL眺めてたらがくぞさんが
Lens の匂いを感じる http://t.co/KGFPNSLSP2
— がくぞ (@gakuzzzz) 2015, 7月 15
ってpostしてて「あ!このデータ構造、Lensのサンプルで見たことあるやつだ!」 が再生されたので(?)、やってみました。
もちろんScalaでLensといえばMonocleだよね!!*1
前置き
その1
何も考えずに各case classに対してLensを定義してやってみます。
ちなみに、&<-?
はapplyPrism
、^|->
はcomposeLens
、^<-?
はcomposePrism
のエイリアスメソッドです。
このsome
はmonocle.std.option#some
で、Monocle標準で提供してるPrismです。
b.value
がOption[A]
なので、a.value
する為にOption[A] => A
が必要だったので使用しています。
結果は以下の通り。
scala> Example1.good
res0: Option[Int] = Some(1)
scala> Example1.bad
res1: Option[Int] = None
良さそうですね。
その2
some
分冗長な気がしたので、_b
と_d
をPrismにしてみます。
これでsome
がなくなった分短く書けるようになりました。
もちろん、同じ結果が得られます。
scala> Example2.good
res2: Option[Int] = Some(1)
scala> Example2.bad
res3: Option[Int] = None
まとめ
どちらにせよCoffeeScriptっぽく書けるわけでもなく、@xuwei-k さんの
「Scalaに存在演算子を求めるのは間違っているだろうか」の解答例 - scalaとか・・・
val x: Option[Int] = OptValue(edcba).wrap._value._fuga._hoge._bar._foo
みたいな感じでOptionを意識することなく繋げることは出来ませんでした。 とはいえ、macroもType Dynamicも使ってない*2ので、それなりにシンプルにかけてるはず...
もっと短くカッコよく書ける方法があれば教えて下さい!!
おまけ
コードの表現的に負けてしまった(?)ので、Lens/Prismを使ったメリットを挙げておくと
のようにgetだけでなくsetやmodifyもできるようになります。やったぜ!
ScalaのStream#filterNotが壊れてた件
既知のbugのようですが、知らずに1時間無駄にしてしまって激おこなのです!!(
先日2.11.7が出ましたが、修正が入るのは2.12-M2っぽいので暫くこのままですね。 [SI-8627] Stream#filterNot broken, should be overridden in Stream - Scala
手元でもすぐに確認できるので、皆さんも是非ハマってみてハマらないようにしてくださいね!
Welcome to Scala version 2.11.7 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_25). Type in expressions to have them evaluated. Type :help for more information. scala> Stream.from(1).filter(_ > 5) res0: scala.collection.immutable.Stream[Int] = Stream(6, ?) scala> Stream.from(1).filterNot(_ > 5) // そして、返答はなく、只々MBPのCPUファンが唸りを上げるのみ
追記: あの悲しき2.11.3事件の原因だった模様。。。情報ありがとうございます!
@AoiroAoino これ http://t.co/xclriGFpXg の原因になったという意味で有名な(?)やつですね(という直接役に立たない余談)
— Kenji Yoshida (@xuwei_k) 2015, 6月 25
MonocleのapplyLensメソッドの使い方
去年末くらいからぼちぼち触り始めてたけど今更知った。
使い方
例えばこんな感じのデータ構造(とLens)が定義されてるとして、*1
@Lenses case class Address(street: String, postalCode: String) @Lenses case class User(name: String, age: Int, address: Address)
Lensを合成していってgetしたり、modifyしたりするわけですが、
import monocle.syntax.apply._ import Address._, User._ // 生成されたLensの為に val me = User("Aoino", 24, Address("street", "xxx-xxxx")) scala> address composeLens street get me res1: String = street // ^|-> はcomposeLensのエイリアスメソッド scala> (address ^|-> street).modify(_.capitalize)(me) res8: User = User(Aoino,24,Address(Street,xxx-xxxx))
applyLensを使うと以下のようにかけます。
scala> me applyLens address composeLens street get res13: String = street // &|-> applyLensのエイリアスメソッド scala> me &|-> address ^|-> street modify(_.capitalize) res16: User = User(Aoino,24,Address(Street,xxx-xxxx))
使わなかった時よりも若干見やすく、流れるようなインターフェース感()が増しましたね。おつおつ。
実装
単純に暗黙的変換でメソッドが追加されてる(ように見える)だけです。
ちなみに
タイトルにはapplyLensって書いてますが、ほかにも以下のメソッド共が存在します。
method | aliase |
---|---|
applyTraversal | &|->> |
applyOptional | &|-? |
applyPrism | &<-? |
applyLens | &|-> |
applyIso | &<-> |
で、一応composeXXXもまとめると
method | aliase |
---|---|
composeTraversal | ^|->> |
composeOptional | ^|-? |
composePrism | ^<-? |
composeLens | ^|-> |
composeIso | ^<-> |
初見は「はぁ???」ってなりますが、composeと一緒に眺めると&か^の違いしかないので、覚えるのはそんなに苦労しないんじゃないかと。*2
まとめ
ってなわけで、これでますますScalaでLens沼欲が高まりますね!!
Lens&Prism勉強会でMonocleについて発表してきた
とっても今更なのだけど、記録として。
↑にLT枠で参加してきた。
資料は
www.slideshare.net
色々あって、前半部分参加できなかったのだけれど、TL眺める限りとても濃い話だったっぽいので、ちょっと残念。
当日は予想以上の人で、緊張のため話そうと思ってたこと半分くらい飛んじゃって、テンパって足プルプルしてたけど、まぁなんとかなって良かった。
今回の発表内容は「理論はよく分からないけど、使い方分かってきたから実践で使ってみた」的な話だったので、もし次回があるのなら今度は理論的な話とかもできるようになりたいなーとか思ってます。
当日の準備、懇親会、片付けに一切参加できずですんませんでした、、、
また機会があれば、今度こそお手伝いできればと!
みなさんお疲れ様でした!ありがとうございました!!