ScalaとRust用のZeroFormatterライブラリを作り始めた
あけましておめでとうございます。 進捗どうですか? 私はダメです。
新年早々バイナリフォーマッターの話をします。
ZeroFormatterは、昨年の11月頃にneueccさんが公開したバイナリシリアライザーです。 詳しい話?は実装者の記事を読んでください。
ここで重要なのは上記記事の一文です。
と、いうわけで、言語がC#のみってのはさすがに普通に欠点なんですが、整備してみたんで多言語サポートよろしくお願いします、みたいな(?)。
おおなるほどな、と思いながら当時はスルーしていたのですが、年末に改めてREADMEを眺めていたら気力が湧きあがったので作ることに決めました。 まぁ、一番の決め手はCross PlatformでStageが明確に分かれていることですね。 なるほど、最初はeager-evaluationで良いなら徐々に育てれば良いかみたいな。
Scala版
https://github.com/pocketberserker/scala-zero-formatter
現時点ではshapelessにべったりです。 あとなんとなくScala.js対応してます。 コードは汚いです(おい)。 ScalaのバージョンやJVMかScala.jsかどうかで動かないものがあったりもするのですが、まぁ、そのうちどうにかします(願望形)。
以下簡単な説明
- Object、Union、EnumをそれっぽくC#に似せて作った
- ADTをフォーマットしたいならUnionを使うしかないという制限がありますが、まぁ、UnionKeyを見つける必要があるのでしょうがない
- Formatterの自動導出
- shapelessによるゴリ押し
- そのため、生成に失敗した際のエラーメッセージが"could not find implicit parameter"という情報量のなさに…
- 使い方はシンプル
そして結構詰まったりもしました。
- オブジェクトのデシリアライズ時にHListの各要素のFormatter型クラスが取得できない
- Coproductの各型のFormatter取得に苦戦
- HListと同じ問題だが、CoproductにfillWithはない(あったら困る)のでさらに苦戦
- 結局以下の順序で無理やり取得した
- Coproduct.lengthを取得
- nat.RangeでHListを生成
- HList.foldRightで各要素の操作時にCoproduct.Atを使って型を導出
- 取得した型にたいしてGenericやHListを駆使してFormatterを取得する
- Annotationやら継承ベースやらでScalaらしくない?
- Indexというメタデータを持たせる方法がAnnotation以外に思いつかなかった…
- case objectがCoproductでの生成対象にならない?のでEnumは仕方なくcase classのみに固定
- UnionでKey用のFormatterインスタンスが取得できない
StructやUnionを継承ベースにしたのは成功か失敗かいまいち判断ついてないですね…。 あと、自動生成にこだわらずにやれば年末年始休暇を全部溶かすことはなかったでしょう。
Scala版の今後の展望。
- 型レベルIndex
スタック溢れを防ぐ特定の書き方をした時だけStackOverflowになる…同じ要素数、型定義でも別の書き方をすれば回避できているのが謎TailRec版を実装している途中だがimplicit parameterが解決できずに困っている- 同じような話を発見したのでcachedImplicitを試したら動いた https://github.com/milessabin/shapeless/issues/345
- ただし2.10.6ではコンパイルエラーになる…Unionもサポートできないのでそのうちdropする
- Stage2サポート
- 劣化Lensみたいなものを作る想定
- Unsafe?
Rust版
Rustはαくらいのときに少し触って、むーりぃーと投げ出して以降ほったらかしていたのですが、さすがになんかもう一つぐらい言語の手札を増やしたい感があったのでやるやる詐欺状態に…。
と、そんなときによく考えたらZeroFormatterというお題があるじゃないですか。 かつてmsgpackを言語入門のお題にしてきた身としては格好の題材ですね?
というわけで作り始めました。
https://github.com/pocketberserker/zero-formatter.rs
まだprimitive typeとstringくらいしかないですが…そのうち頑張る、ということで。
いまのところの感想としては、Scalaのshapelessとは違う意味でコンパイルが全然通らない…といったところでしょうか。 Rustのお作法がわからない状態で作っているのでそのうちRust強者に良い書き方を教わりたい…。
ZeroFormatterの所感
うまい感じにまとまっているなーというのが実装してみた現時点での感想ですね。 nullの扱いがNullableとその他の型で異なるのが困りどころではありますが、そこさえなんとかなればStage1の実装は容易です。 いやまぁ、最速を目指すなら(そしてStage2対応するなら)それだけではもちろんだめですが、初期実装が比較的簡単に作れることは重要なんじゃないかと思います。 ないなら作る、の敷居が低いのは良いことです。
というわけで所感列挙。
- nullの規定が明確なこと
- has valueか最初が-1かどうかで判断できるのでOptionを標準にもつ言語ならそっちにマッピングさせることができる
- フォーマットによってはこのあたりが面倒な場合があるので…
- Union
- 昔欲しかった(切実)
- バイナリサイズが富豪っぽく感じるけどトレードオフだし仕方なさそう
- Indexは人によって好き嫌いあるかも?
- とはいえそれはprotobufも同じだし、そういう時代の流れなのかも?
msgpackやprotobufなどとと張り合うならJSONとの相互変換もドキュメント化されているほうがいいんじゃないかとか、まぁいろいろありますが、そこはコミュニティができるかどうか次第なんじゃないでしょうか。
それ以外だとC#以外の実装がどこまで最速の座を奪えるかどうか、もあるのかな?
あと別言語での通信に使うとなればジェネレータが必要になりそうですが、あのSchemaなら誰かがえいやでプラグイン機構付きのものを作れってくれるでしょう…という楽観もあったりなかったり。
おまけ
F#拡張?知らない子ですね。
…いや、決して知らないわけでもissueを眺めるにとどめているわけでもないのですが…。 なんというかこう、ILとにらめっこすれば作れることはわかっているし技術的な問題はおそらくないはずだけど、そこに面白さはないんですよねぇ…判別共用体に対応させてパターンマッチできるようにするくらい? うーん、それだったら欲しい人がそのうち作るでしょう、みたいな。
…とか言ったら未来の自分にブーメランになりそうなのでここまでにします。
なお”気が向いたらErlangかElixirでも作ってみたいかも?”という気持ちはある。
おわりに
いろいろ書きましたが、ZeroFormatterはまだ全然実装が揃っていないみたいで、あなたがいますぐ実装すればその言語でのZeroFormatter第一人者になれるのでおすすめです。 最適化の勉強にもなりますからね。