scala-zero-formatterのパフォーマンス改善やakka-httpサポートとか

https://github.com/pocketberserker/scala-zero-formatter/releases/tag/v0.7.0

表題の件以外にもlz4とzstdへの変換をサポートしてみたりしました。 この2つはあくまでとりあえず程度なので速度そんなにでないですが……。

速度改善の話

  • 余分な型クラスを除去
    • 一時期のバージョンでいれていたが今は無くても動く(もしかしたら昔も動いたのかもしれない)
    • ついでに一部パッケージを統合
  • unsafeモジュールでのStringのエンコード方法変更
  • case classの全フィールドのバイナリサイズがわかっている場合はmacroでバイナリサイズを算出する
    • 最後のresize(arrayコピー)が減る

内部でprotobuf-javaのUnsafeを使っているので、そりゃScalaPBと速度で勝負できるよね…という。 とはいえデシリアライズがまだ若干遅いことがある問題?もあるのでなんとかできないか調査中。

akka-http

MarshallerとUnmarshallerをつかえば簡単にできそうだったので作ってみた、という。 JSON系ライブラリはspray-jsonは公式さぽーとだったり、他のライブラリも既にライブラリがあったりします。

msgpack用のライブラリもそのうち誰かがつくるのではないでしょうか?

lz4, zstd

最初に書きましたがまだお試しパッケージです。 そのため思ったほど速度がでないと思います(LZ4のデシリアライズはその特性上十分早いけど)。

あと、バイナリサイズを考慮して使うか決めないと圧縮前よりサイズが増える、なんてこともなくはない?

MessagePack-CSharp用の 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の真似事をするかどうか悩みつつ、要望があれば随時対応かなと考えています。

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

判別共用体のUnionCaseにAttributeを付与する

必要に迫られたので調べた。

open System

// AttributeTargets.Property以外はエラー
[<AttributeUsage(AttributeTargets.Property, Inherited = false); Sealed>]
type TestAttribute() =
  inherit Attribute()

// fieldがない場合:
//   AクラスのB getプロパティにつく
type A =
  | [<Test>] B

// fieldがない場合:
//   CクラスのNewDメソッドにつく
type C =
  | [<Test>] D of int

struct unionの場合も特に違いはなし。

AttributeTargets.Propertyなのにメソッドかよーみたいな気持ちはありつつ、でもまぁそこしかないかみたいな。

`dotnet new`用のテンプレートエンジン

不定期.NET Core SDK周辺調査メモ。

GitHub - dotnet/templating: This repo contains the Template Engine which is used by dotnet new

かつてはdotnet new3と呼ばれていたらしい。 RC4時点でこいつがdotnet newで使われるのでどうでも良い知識ではある。

https://github.com/dotnet/templating/tree/c3634ad9fe235fa23ed1a76149bf24f3fb9c41c0/template_feed

このあたりで標準テンプレートが管理されている。

https://github.com/dotnet/templating/blob/c3634ad9fe235fa23ed1a76149bf24f3fb9c41c0/src/Microsoft.TemplateEngine.Cli/New3Command.cs#L888

現時点では隠しコマンドだったり、未実装なコマンドもちらほら。

キャッシュ情報は~\.templateengine\dotnetcliあたりのようだ。 ja-JP.templatecache.jsontemplatecache.jsonを見ればどんなテンプレート情報を編集できる(が、手作業の修正はおすすめしない)。

NuGetにテンプレートをpublishしておけばいい感じに選べるようになる未来がくるのだろうか? Visual Studio用のテンプレートを作らなくてもVisual Studioで使えるとかであれば結構便利そうだ(実際どうなのかは知らない)。

F# の型拡張を使って定義したメソッドを C# から呼び出す

Donさんに教えてもらいました。

リフレクションを使って呼び出すだけなのでC#限定というわけではないです。

using System.Reflection;
using Microsoft.FSharp.Reflection;

~ 略 ~

typeof(FSharpReflectionExtensions).GetTypeInfo()
    .GetMethod("FSharpType.IsRecord.Static")
    .Invoke(null, new object[] { typeof<string>, null });
// 第2引数のoptionを指定するつもりがないなら、.NET的にはnullでもOK

型名.メソッド名.Staticという形式になるらしい。 ILには存在するものの、C#がドット付きメソッドを呼び出せないのでこういう方法をとる必要があるようです。

使う機会はないでしょうがまぁ勉強になった、ということで。

ZeroFormatter.FSharpExtensionsのF# 4.1対応

FSharp.Core 4.1.0がNuGetにpublishされていたので、ZeroFormatter.FSharpExtensionsでF# 4.1の型を扱えるようにしました。 ついでにプロジェクト構成を.NET Core SDK RC4のものに全面修正しています。

NuGet Gallery | ZeroFormatter.FSharpExtensions 0.3.0

いやまぁ、正確には.NET Coreでのビルドに失敗していて(ビルドは通るけど空のdllができていた…)対応せざるをえなかっただけなんですけどね(ひどい)。

対応した型

  • struct tuple
    • 内部的にはSystem.ValueTuple
    • Nullableは非対応
  • struct record
  • struct union
    • Resultはここに属する

わかりやすいですね。

Nullableなstruct tuple用のFormatterを用意しなかったのはF#だからOptionでいいんじゃない、みたいな理由です。 C# 7のことを考えれば本家がValueTupleをサポートすべき事案なのですが、忙しそうなのでいつ次のバージョンがリリースされるかわからない…。 pull reqしようにも.NET Core SDK preview2が手元にないのでビルドもままならないので「project.jsonめ…」という気持ち。

余談

.NET Core対応させようとしたときに下記の問題にぶつかりました。

https://github.com/Microsoft/visualfsharp/issues/2455

FSharp.Core 4.1.0のnetstandard1.6ではFSharp.Reflection.FSharpTypeFSharp.Reflection.FSharpValueのいくつかのメソッドが存在せず、型拡張で定義されているメソッドにはC#からアクセスできないのでレコードや判別共用体を判定する手段がなくあわや詰みかけたという…。 C#からF#用のメソッドを呼び出す人はそうそういないので気付かないのも仕方ない。 幸いドンさんから回避策を教えていただけたのと(ただしリフレクションを使う…)、F#自体にも変更がはいったのでバージョンがあがれば心配しなくてすみます。

それにしてもこのライブラリ、開発中にいろんなものを踏み抜くなぁ。

F# のJSON事情

現時点でF#のJSON事情をぱっと思い出せなかったので、知っている範囲でメモしておくことにします。

あらかじめ断っておくと、私見にまみれているかつ抜けているライブラリがあるかもしれません。

DataContractJsonSerializer

標準にあるやつ。 当然ながらF#のいくつかの型には対応していない(意図しない出力になったりエラーになったり?)。

一つのプロジェクトに閉じていて他のライブラリを使うまでもないくらい小さいJSONを扱いたい時に使える? FSDNはまさにそんなJSONだったのでこいつを採用しています。

*** 追記

拡張を用意してあげればもちろんF#の型も対応できます(ただ拡張ライブラリ入れるなら別のライブラリを選ぶ…)。

余談

Suave本体に含まれているSuave.Jsonモジュールは内部でDataContractJsonSerializerを使っています。 例外はmapJsonWith関数で、こいつはdeserializeとserialize用の関数を渡す形になっています。 つまり、Suaveで他のJSONライブラリを使いたいならmapJsonWithを用いて各ライブラリの関数を作るか、直接そのライブラリを使うかになります。 まぁ、mapJsonWithを使わない場合もライブラリの入れ替えが楽になるようにオレオレJsonモジュールを作るのが良いと思います。

Json.NET

http://www.newtonsoft.com/json

.NETでJSONといえばこれでしょ、みたいなやつです。 いろんなライブラリ、フレームワークがこいつを使っているので必然利用頻度が高まります。

素の状態でF#の型をシリアライズすると望んだものでないデータが出来上がるので注意が必要です。

個人的なあれをいうと、こいつを使う場合はclassや.NET標準のデータ型のみで型を定義しますね。 F#用の拡張を用意するのは面倒だし、かといって他のライブラリがメンテナンスされているかというとうーん…となりやすいので。 レコードやlist、その他もろもろを使わない覚悟が必要になりますが、まぁAPI用の型と内部で使う型って別物になりがちなので入口出口くらいしかきにならないというか。

FSharpLu.Json

https://www.nuget.org/packages/Microsoft.FSharpLu.Json/

珍しくMicrosoft organizationに存在するF#プロジェクトでJson.NETに依存しています。 存在感は薄い。

Newtonsoft.Json.FSharp

https://github.com/haf/Newtonsoft.Json.FSharp

SuaveコミッターのHenrik Feldtさん作……ですがdescriptionに以下の注意書きがあります。

I recommend using Chiron instead of Newtonsoft

どうやら後述するChiron推しのようです。 使う機会はないでしょう。

Chiron

https://github.com/xyncro/chiron

HaskellのAeson的なライブラリです。 Lensがおまけでついてきます。

そっち系に慣れている人は(アプリカティブスタイルも使えるので)使いやすいかもしれませんが、慣れない人は敬遠しそうな感じ。

FSharp.Data

https://github.com/fsharp/FSharp.Data

TypeProviderの例としてよく知られているやつですね。 型を自動生成してくれるのはこいつくらいじゃないでしょうか(JSONは往々にして自動生成が役立つことってあまりない気もしますが…)。

解析向けに使うには強いけどAPIに使うには向かない印象を私は持っています(あくまで個人の感想です)。

FsPickler.Json(番外)

https://www.nuget.org/packages/FsPickler.Json/

機能がモリモリなバイナリシリアライザのFsPicklerのJSONプラグイン的なやつ。 JSONライブラリというわけではないので番外扱い。

ドキュメントに以下の記載があります。

FsPickler is NOT:

  • a general-purpose XML/JSON/BSON framework.
  • a library designed for cross-platform communication.
  • a library designed with version tolerance in mind. Avoid using for long-term persistence.

まぁ、そりゃそうですよね。

パフォーマンス

知らぬ。

いや、重要なのは理解しているのですがよっぽど遅くない限りは速度気にならない部分でしか使わない…真面目に速度が必要ならバイナリシリアライザを使うからなぁ。

真面目に使うつもりの人は計測しましょう。

まとめてきな何か

正直、SuaveでJSON APIを組み立てる時にいつもライブラリ選定に悩まされます。 どれも一長一短あるし、他の言語を触っている身としては「auto deriveしつついい感じに使わせてくれ〜」みたいな気分になるわけですよ。

一応個人的な使い分けを書いておくと

  • Chiron
    • 個人的な慣れの問題で使う
  • FSharp.Data
    • Readerしか必要ない時
  • Json.NET
    • 他のライブラリがこれに依存している時は問答無用で採用
    • 同じプロジェクトで複数のJSONライブラリを使いたくない
  • DataContractJsonSerializer
    • 上記を使うことすら億劫なとき

こんな感じですかねぇ。

結局F#でもJSONライブラリは氾濫しているのでした…つらい。