読者です 読者をやめる 読者になる 読者になる

独学大学情報学部

主にScala、たまにHaskell、稀に数学

Monocleのコミッターになりました

夕飯食べてる最中にリプライ通知が来て、確認したらなってた。

f:id:Aoino:20150923223135p:plain

一応お誘い(?)っぽい話は一昨日くらいにGitterで直接受けていました。 正直なところ本当にあたしでいいんですか?って気持ちもあったりしますが、最近LensやPrismが楽しくてしょうがないし、引き続きPR投げたりはしようと思ってたので、せっかく頂いた機会だし頑張ってみようと思います。よろしくお願いしますm(_ _)m

追記:2015-10-28
書けと言われたので会社の方のブログにも書きました。

techlog.mvrck.co.jp

追記:2015-12-19
今更だけど、タイトルわかりにくかったので修正した。

before:
 MonocleのCollaboratorになりました
after:
 Monocleのコミッターになりました

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と似たような構成になっていて*2monocle/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出してみるといいんじゃないですかね?チャンスです。

^|->> や ^<-> って書くのが嫌になってきた

見出しの記号はそれぞれcomposeTraversalcomposeIsoってメソッドエイリアスで、使う場面というか使い方は Monocleのexample

「Scalaに存在演算子を求めるのは間違っているだろうか」をLens/Prismで解いてみる - 独学大学情報学部

とか見ればなんと無くわかるんじゃないかと思います。 とりあえず、compose系メソッド名と記号メソッド名の対応は覚えたんだけど、記号メソッド入力するときにほぼ右手のみでしかも4-5文字ってめちゃくちゃキーボード打ちにくくて不便なので、何か共通の記号一文字でできないかと思ってやってみたのが、これ。

orz · aoiroaoino/Monocle@5cf5d9a · GitHub

以前のv0.x系の頃には一律composeってメソッド名だったのだけど、こんな感じでoverloadするとうまく動作しない問題があった。それを暗黙の変換かますと回避できたりしないかなーってなんと無く思ってやってみたけど、そんなことはなかったし、詳しく追うのも疲れたのでお蔵入りコードとなりました。南無三。この問題、回避策をご存知の方いれば教えて頂きたく。

まとめ

ここ三週間くらいプライベートではこんな感じの事やってた。 英語は適当過ぎるの良くないけど、それなりに頑張るとそれなりに通じるっていう知見を得たので、議論できる程度まで上げたいですね、英語力。

追記 2015-09-16

アラカルト形式importはscalazが最初ではない模様。

追記 2015-09-17

cats化難しいんじゃないの?とかって内容の記事書いて次の日にcats化のブランチがremoteにpushされたし。 うぅむ、PR出てくるのも時間の問題っぽい...。

Comparing master...cats · julien-truffaut/Monocle · GitHub

*1:結果的には入れてないです。あんまりメリットなかったので。

*2:独習Scalazにはアラカルト形式って呼称がありますね。そもそもこのデザパタみたいなのはScalazが原点なんですかね? 追記 2015-09-16参照。

*3:さすが

*4:https://gitter.im/julien-truffaut/Monocle?at=55b27a7469ee33730f44380b

*5:警告がエラーになる -Xfatal-warnings をつけてるため

「Scalaに存在演算子を求めるのは間違っているだろうか」をLens/Prismで解いてみる

元の記事はこちらです。

Scalaに存在演算子を求めるのは間違っているだろうか - だいたいよくわからないブログ

TL眺めてたらがくぞさんが

ってpostしてて「あ!このデータ構造、Lensのサンプルで見たことあるやつだ!」 が再生されたので(?)、やってみました。

もちろんScalaでLensといえばMonocleだよね!!*1

前置き

その1

何も考えずに各case classに対してLensを定義してやってみます。

ちなみに、&<-?applyPrism^|->composeLens^<-?composePrismエイリアスメソッドです。

このsomemonocle.std.option#someで、Monocle標準で提供してるPrismです。 b.valueOption[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もできるようになります。やったぜ!

*1:Scalazにもshapelessにもあります。

*2:Monocle側のGenLensはマクロでLens生成してますけどね

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事件の原因だった模様。。。情報ありがとうございます!

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))

使わなかった時よりも若干見やすく、流れるようなインターフェース感()が増しましたね。おつおつ。

実装

単純に暗黙的変換でメソッドが追加されてる(ように見える)だけです。

https://github.com/julien-truffaut/Monocle/blob/v1.1.1/core/src/main/scala/monocle/syntax/Apply.scala#L10-L19

ちなみに

タイトルにはapplyLensって書いてますが、ほかにも以下のメソッド共が存在します。

method aliase
applyTraversal &|->>
applyOptional &|-?
applyPrism &<-?
applyLens &|->
applyIso &<->

で、一応composeXXXもまとめると

method aliase
composeTraversal ^|->>
composeOptional ^|-?
composePrism ^<-?
composeLens ^|->
composeIso ^<->

初見は「はぁ???」ってなりますが、composeと一緒に眺めると&か^の違いしかないので、覚えるのはそんなに苦労しないんじゃないかと。*2

まとめ

ってなわけで、これでますますScalaでLens欲が高まりますね!!

*1:マクロアノテーションの力により、コンパニオンオブジェクト内にcase classで定義されてるフィールド名と同名のLensが定義されます。詳しくはこちら

*2:とはいえ、右手onlyで打つ記号共なので逆に打ちにくいっていう