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

MessagePack-CSharp用の F# 拡張ライブラリを作った

F#

MessagePack-CSharpがリリースされた話を読んですっごーいと感心していたら

という通知が飛んできたので即興で作りました。

https://github.com/pocketberserker/MessagePack.FSharpExtensions

拡張でサポートする型

  • list
  • set
  • map
  • unit
  • option
  • 判別共用体
    • FSharp.Coreのものはフィールド名ベースの変換のみサポート
      • ResultやChoice
  • Async
    • シリアライズAsync.RunSynchronouslyを使っているのでちょっと微妙かも?

このあたりに対応させています。

レコードは実は本家のライブラリ側でよしなにやってくれます。 つまり私が実装した拡張を導入しなくても苦労することなく速度の恩恵にあやかれます。

互換

判別共用体に関しては一応程度ですがC#側のUnionと相互変換できます。

  • Tagプロパティでケース判定(0, 1, 2…)
  • MessagePackObject属性が付いている場合はシーケンシャルなIntKeyを勝手に付与してフィールドをシリアライズ
  • 属性が付いていないか、プロパティ名を使う指定になっていればフィールド名を使う
    • フィールド名が省略されている場合はコンパイラの自動生成によるItem1とかになるので、それを避けたいなら名前をつけましょう
    • 属性無指定でデシリアライズできるようにしておかないと厳しい場面もあるかな、という気持ち

例えばF# 4.1から導入されたResultの具体例Result<int, string>をstructで表すとこんな感じになるはずです。

[<Union(0, typeof<CsOk>)>]
[<Union(1, typeof<CsError>)>]
type CsResult = interface end

and [<Struct; MessagePackObject(true)>]CsOk(resultValue: int) =

  member __.ResultValue = resultValue

  interface CsResult

and [<Struct; MessagePackObject(true)>]CsError(errorValue: string) =

  member __.ErrorValue = errorValue

  interface CsResult

型パラメータはさすがにどうにもならない気がしています(ほんとか?)

ところで、他の言語で生成されたバイナリを扱ったりその逆パターンを考えるとプロパティ名を小文字にするオプションが欲しい気もするがどうなのでしょう? そういうときにMessagePackを採用することってあまりないから必要ない可能性も高いわけですが、はてさて。

がんばったところ

判別共用体のデシリアライズが(現行の)ZeroFormatter.FSharpExtensionsのものより洗練されています(たぶん)。 MessagePack-CSharpの知見とか、改めてFSharp.CoreのILを眺めた関係でBox化が挟まらなくなったことが大きいですね。

他の部分はそんなに頑張らなかったわけですが、MessagePack-CSharpのAPIがよくできていているので頑張らなくてもなんとかなる、というのが正しいです。

いやほんと素晴らしい、さすがの一言です!

今後

FsPicklerの真似事をするかどうか悩みつつ、要望があれば随時対応かなと考えています。

パフォーマンス調査結果はそのうち掲載します。