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
の紹介でした。
素敵なマクロライフを〜。
Monocleのコミッターになりました
夕飯食べてる最中にリプライ通知が来て、確認したらなってた。
I want to welcome Naoki (aka @aoiroaoino) as maintainer of Monocle. Thanks for you help!
— Julien Truffaut (@JulienTruffaut) September 23, 2015
一応お誘い(?)っぽい話は一昨日くらいにGitterで直接受けていました。
正直なところ本当にあたしでいいんですか?って気持ちもあったりしますが、最近LensやPrismが楽しくてしょうがないし、引き続きPR投げたりはしようと思ってたので、せっかく頂いた機会だし頑張ってみようと思います。よろしくお願いしますm(_ _)m
追記:2015-10-28
書けと言われたので会社の方のブログにも書きました。
追記: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と似たような構成になっていて*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出てくるのも時間の問題っぽい...。