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

ScalaとRust用のZeroFormatterライブラリを作り始めた

Scala Rust

あけましておめでとうございます。 進捗どうですか? 私はダメです。

新年早々バイナリフォーマッターの話をします。

ZeroFormatterは、昨年の11月頃にneueccさんが公開したバイナリシリアライザーです。 詳しい話?は実装者の記事を読んでください。

ここで重要なのは上記記事の一文です。

と、いうわけで、言語がC#のみってのはさすがに普通に欠点なんですが、整備してみたんで多言語サポートよろしくお願いします、みたいな(?)。

おおなるほどな、と思いながら当時はスルーしていたのですが、年末に改めてREADMEを眺めていたら気力が湧きあがったので作ることに決めました。 まぁ、一番の決め手はCross PlatformでStageが明確に分かれていることですね。 なるほど、最初はeager-evaluationで良いなら徐々に育てれば良いかみたいな。

Scala

https://github.com/pocketberserker/scala-zero-formatter

現時点ではshapelessにべったりです。 あとなんとなくScala.js対応してます。 コードは汚いです(おい)。 ScalaのバージョンやJVMScala.jsかどうかで動かないものがあったりもするのですが、まぁ、そのうちどうにかします(願望形)。

以下簡単な説明

  • Object、Union、EnumをそれっぽくC#に似せて作った
    • ADTをフォーマットしたいならUnionを使うしかないという制限がありますが、まぁ、UnionKeyを見つける必要があるのでしょうがない
  • Formatterの自動導出
    • shapelessによるゴリ押し
    • そのため、生成に失敗した際のエラーメッセージが"could not find implicit parameter"という情報量のなさに…
  • 使い方はシンプル
    • ZeroFormatter.serializeZeroFormatter.deserializeを呼ぶだけ
    • このあたりはC#版がシンプルなAPIだったので引き継がれた感じ?

そして結構詰まったりもしました。

  • オブジェクトのデシリアライズ時にHListの各要素のFormatter型クラスが取得できない
    • シリアライズは対象の値があるのでその型からFormatterをimplicit pamrameterで取得できたが、デシリアライズに値はない
    • ないので、HList.fillWithnull.asInstanceOf[T]で無理やりHListを生成し、そこからimplicit parameterでFormatterを引っ張ることにした
  • Coproductの各型のFormatter取得に苦戦
    • HListと同じ問題だが、CoproductにfillWithはない(あったら困る)のでさらに苦戦
    • 結局以下の順序で無理やり取得した
      1. Coproduct.lengthを取得
      2. nat.RangeでHListを生成
      3. HList.foldRightで各要素の操作時にCoproduct.Atを使って型を導出
      4. 取得した型にたいしてGenericやHListを駆使してFormatterを取得する
  • Annotationやら継承ベースやらでScalaらしくない?
    • Indexというメタデータを持たせる方法がAnnotation以外に思いつかなかった…
  • case objectがCoproductでの生成対象にならない?のでEnumは仕方なくcase classのみに固定
  • UnionでKey用のFormatterインスタンスが取得できない
    • A <: Union[B]と書いていたらimplicit parameterが解決できなくなった
      • A <: Union[_]なら大丈夫
    • しょうがないので、Unionをtraitからabstract classにしつつ[T: Formatter]と定義し、key用のシリアライズ、デシリアライズメソッドを追加

StructやUnionを継承ベースにしたのは成功か失敗かいまいち判断ついてないですね…。 あと、自動生成にこだわらずにやれば年末年始休暇を全部溶かすことはなかったでしょう。

Scala版の今後の展望。

  • 型レベルIndex
    • Natを使って型の重複をコンパイル時に判別する
    • …ということをやろうとしたがops.nat.ToIntがどうしても解決できずに諦めている
  • スタック溢れを防ぐ
    • 特定の書き方をした時だけStackOverflowになる…
    • 同じ要素数、型定義でも別の書き方をすれば回避できているのが謎
    • TailRec版を実装している途中だがimplicit parameterが解決できずに困っている
    • 同じような話を発見したのでcachedImplicitを試したら動いた https://github.com/milessabin/shapeless/issues/345
    • ただし2.10.6ではコンパイルエラーになる…Unionもサポートできないのでそのうちdropする
  • Stage2サポート
    • 劣化Lensみたいなものを作る想定
  • Unsafe?
    • sun.misc.Unsafeを使えばもっと早くなることは明らか
    • とはいえJava 9との兼ね合いがあるからなぁ
    • Scala.jsやScala Nativeはたぶん別実装にわかれるんじゃないだろうか

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第一人者になれるのでおすすめです。 最適化の勉強にもなりますからね。

Persimmonの.NET Core対応

F#

これはF# Advent Calendar 18日目の記事です。 そして.NET Core Advent Calendar 18日目の記事でもあります。 盛大に遅刻しました。

qiita.com

qiita.com

今回はPersimmonという、私が開発にかかわっているF#向けテスティングフレームワークの話をします。

スティングフレームワークを.NET Coreに対応させるためにやるべきことはいくつかあります。

  1. 本体を.NET Coreに対応させる
  2. test communication protocolに従ってランナーを実装する
  3. 周辺ライブラリを.NET Coreに対応させる

Persimmonはこのうち最初の二つが完了しているので、他に依存のないプロジェクトであれば.NET Core版のPersimmonを試すことができます。

dotnet-test-persimmon

.NET Core CLI用にdotnet-test-persimmonを実装しました。 内部実装はF#非依存です(ランナー自体のテストはF#で書いていますが)。

CLIでの実行は特に問題ないのですが、Visual Studioのテストエクスプローラー連携がまだ若干怪しい挙動なのでお試し程度にどうぞ。

周辺ライブラリ

ほぼ未対応です。 というのも、まだF#自体の.NET Coreサポートの先行きが不透明なので労力を割くべきか決めあぐねているからです。

遅刻したわりに短い記事になってしまった…。

コンピュテーション式の展開結果を可視化するツールComVuを作った

F#

この記事はの13日目です。

qiita.com

今回は過去に作ったComVuというツールの話をします。

https://github.com/pocketberserker/ComVu

nugetでダウンロードできます。

これはなに

ComVuはコンピュテーション式を機械的に展開し、結果を表示するライブラリとツールの総称です。

名前の由来ですが、F#談話室終了後の食事の席で「こういうツールほしい」という話になった際、「こんぴゅてーしょんえくすぷれっしょんびじゅあらいざー…昆布だ!」という流れで決まりました。

ComVu.Coreが解析部分、ComVuがWPFによるツールとなっています。

外部ライブラリの解析

FSharp.Compiler.Serviceを利用してコンパイルや解析を行っているので、#I#rを使えば外部ライブラリのコンピュテーション式も解析可能です。

sequence expressionsの疑似展開

sequence expressionsの実装はコンピュテーション式ではありませんが、にたような形式で作れるよという意味合いを込めて展開結果を表示できるようにしています。

問題点

  • 一部のオペレータや関数名の表示が微妙
    • 記号や関数名ではなくCompiledNameで表示されてもわかるでしょ、という気持ちがあったためさぼりました
  • インデント深すぎ
    • YC.PrettyPrinterを使ったらこんな感じに…外部からインデント幅を指定することが簡単にはできそうにないので、独自実装するしかないのかなぁ
  • Windows以外では使えない
    • ComVu.Coreを使えばあとはUIの問題だけなので、どなたか作ってください

余談

作ったは良いものの、私自身はツール無くてもどうにかなっているので拡張する気力があまり起きないんですよね…。 とはいえよさそうな提案があれば実装するつもりはあるので、issueお待ちしています。

2016年時点でF# 用のライブラリを.NET Core対応させるのは時期尚早だった?

F#

この記事はF# Advent Calendar 2016の12日目の記事です。 また、.NET Core Advent Calendar 2016のの12日目の記事でもあります。

結論

先に個人的な結論を述べておきます。

現在のステータス

若干古めですが、GitHub上のvisualfsharpリポジトリwikiに色々書いていあります。

https://github.com/Microsoft/visualfsharp/wiki/F%23-for-CoreCLR---Status

問題になる点

いくつかのライブラリで.NET Core対応を行った感想です。

  • NuGetのFSharp.Coreに依存させたくない場合は過去と同じ要領でプロジェクトをわけるしかない
    • project.jsonのせい(安らかに眠れ…)
    • NuGetのやつに依存させたくない人は一定数存在する
    • F# 2.0に対応させようとするとこの選択肢しかとれない
  • Visual Studio 2015での開発の厳しさ
    • ハイライト効かない(F# 対応していないので当たり前か)、nugetの挙動が怪しい
    • IDE使わない場合はそんなに苦でもなかった
  • FSharp.Compiler.Serviceが.NET Coreにまだ対応していない
    • わりとネック

これに加えて

  • project.jsonが負債と化すことが確定している
  • TypeProviderはまだサポートしていない
  • Paketもまだ対応できているとは言い難い(?)
    • 9月頃の話。もしかしたら色々と変化しているかも。

といったことを考えると、そこまで急いで対応させる必要はないかと思います。

ただ、AWS LambdaでF#を使うみたいなことをしたい場合、ライブラリが必要なら対応させるしかないでしょうね。

おわりに

まぁ、基本はおとなしくツールが出揃うまで待ちましょう。

F# 4.1から一部のキーワードがunreserveされる話

F#

この記事はF# Advent Calendar 2016の9日目の穴埋め用記事です。

qiita.com

本日はキーワードの話です。

https://github.com/fsharp/fslang-design/blob/8ee13305a7ac559a4c0396681e9501120bbb567e/FSharp-4.1/FS-1016-unreserve-keywords.md

幾つかのキーワードがidentifierとして利用可能になります。 理由は様々ですが(RFCに理由が書かれています)、キーワードとして使うことはないだろうと判断されたものが取り除かれた感じです。

個人的に面白いと思ったのはfunctorですね。

functor - If F# added parametereized modules, we would use "module M(args) = ..."

え、そんなこと考えているの、という気持ちになりました。

F# 4.1からCallerLineNumber, CallerFileName, CallerMemberNameが機能するようになる話

F#

この記事はF# Advent Calendar 2016の7日目の穴埋め用記事です。

qiita.com

今回は標題にあげたCallerLineNumber, CallerFileName, CallerMemberNameの話。

https://github.com/fsharp/fslang-design/blob/8ee13305a7ac559a4c0396681e9501120bbb567e/FSharp-4.1/FS-1012-caller-info-attributes.md

C# 5.0から導入されたCaller Information機能がF# 4.1から使えるようになります。

  • 省略可能なパラメータに属性を付ける
    • デフォルト値を定義したいならdefaultArg関数を使いましょう
  • 関数は対象外
    • Optional Parametersはメソッドにしか定義できないので仕方ない

何が嬉しいかって、これのためだけにFSharp.Compiler.Serviceに依存しなくても良くなることですね。

個人的な利用用途の一つにPersimmonでの情報量増加があります。 assertionに失敗した時にファイル名、行番号が簡単にだせるので嬉しいですね*1

*1:F# 4.1しか表示できない問題は残る

F# 4.1のResult型

F#

この記事はF# Advent Calendar 2016の6日目のものです。 穴埋め用記事です。

qiita.com

今回はF# 4.1から標準となるResult typeの話です。

https://github.com/fsharp/fslang-design/blob/8ee13305a7ac559a4c0396681e9501120bbb567e/FSharp-4.1/FS-1004-result-type.md

ケース名をどうするかみたいな話がありますが、最終的には以下の形に落ち着いたようです。

type Result<'T,'TError> =
    | Ok of 'T 
    | Error of 'TError

また、ResultモジュールにはbindmapmapErrorが定義されています。

さて、標準から成功/失敗を表す型が提供されたのでめでたしめでたし…というわけにはいきません。

  • ライブラリ側は古いF#ともおつきあいしなければならない関係上、独自のResultやChoiceが消えることは当分ないはず
    • F# 4.1と全く同じ型、関数を提供するライブラリをつくって古いバージョンでも同じように書けるようにする手もあるが、うーん?
  • アプリケーション側はある程度置き換わるはず…とはいえ関数が足りないのでオレオレライブラリが氾濫する可能性は残されている

まぁ、もうすぐ2016年は終了するわけですし、2017年はF# 4.0以下を窓から投げ捨てる覚悟をしても良いかもしれませんね?