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

F# ではLiteral属性がついている束縛はpublic constなフィールドになる

F# の内部表現的な話。 前にどこかで人に尋ねられたことがあったので備忘として。

[<Literal>]
let foo = 1

こんなコードを書くと

[Literal]
public const int foo = 1;

こういうコードとだいたい同じILになるよ(実際には他にもコンパイルが勝手に付与する属性があるかも)というただそれだけの話。

ちなみに

let foo = 1

これは

public static int foo
{
    get
    {
        return 1;
    }
}

だいたいこれと同じ(本当はコンパイラが自動付与する属性があるわけだが面倒なので記述を避けた)。

Persimmonをdotnet testコマンドで実行するためのTestAdapter試作

https://www.nuget.org/packages/Persimmon.TestAdapter/

まだメジャーリリースには至っていないのですが、ひとまず動いたのでnugetにあげておきました。 これでようやくdotnet testコマンドでテストができるようになったわけです。

kekyoさんが途中まで作成していたPersimmon.VisualStudio.TestExplorerを色々と書き換えた形になります。 PersimmonのVS拡張プロジェクトはある時期から停滞していたし(Persimmonに関わっている人はだいたい忙しい+私に力がなかった)、古い環境は正直サポートしなくていいかなと思ったので独断でリポジトリをTestAdapter用に作り変えてしまいました。

正式リリース以降の.NET Core SDKVisual Studio Test Platformに従っているので、コア部分はTest Explorer向けVS拡張を流用できるわけです。

dotnet testにはfilterオプションがあるのですが、もしかしたらこれがちゃんと動かないかもしれません。 なんでかというと、Persimmonにfilter機能がないからです! 全実行することを前提で作っていたからね、仕方がないね…そのうち頑張りたい。

というわけで記事自体は短いのですが…最後に、ある程度動く部分まで作ってくださったkekyoさんに感謝を!(これが書きたかった)

Persimmonのアサーション強化策たたき台

私が関わっているPersimmonというF#向けテスティングフレームワークアサーションがとても貧弱です。 どのくらい貧弱かというとassertEqualsassertNotEqualspassfailassertPredignoreResultしかない状況です。

これは(後付けですが)Coreは最小限のアサーションのみにとどめ、より高機能なアサーションは別ライブラリとして提供したいという思想に由来します。 昨今はJUnit5やJavaScript用のテスティングフレームワークで似たような雰囲気を感じます(私の気のせいかもしれませんが)。

さて、Persimmon.MuscleAssertというアサーション拡張が存在します。

Persimmon用アサーションライブラリMuscleAssertを作った - pocketberserkerの爆走

続・そろそろPower Assertについてひとこと言っておくか - ぐるぐる~

2つ目の記事でid:bleis-tiftさんが弱点を書いています。

MuscleAssertの弱点は、一点比較しかできないところです。 そのため、浮動小数点数を含むデータ構造を、浮動小数点数の一致範囲を指定して比較、ということは現状ではできません。 また、大小比較などもサポートしていません。

標準にはassertPredという貧弱なものしかなく、MuscleAssertにも存在しない…これはおそらくそのうち不都合が生じます。 そんなわけで、なんでもいいからとりあえず何か作っておこうと思い立ってできたのがPersimmon.Unquoteです。

GitHub - persimmon-projects/Persimmon.Unquote

これはunquoteと呼ばれるF#向けライブラリのPersimmon用forkです。

Persimmon標準のアサーションを使うと以下のようになります。

assertPred (([3; 2; 1; 0] |> List.map ((+) 1)) = [1 + 3..1 + 0])
Assertion failed.

失敗したこと以外は何も得られないエラーメッセージですね(意図的にそうしているわけですが)。

Persimmon.Unquoteを使うと次のようになります。

open Persimmon.Unquote

assertPred <@ ([3; 2; 1; 0] |> List.map ((+) 1)) = [1 + 3..1 + 0] @>
([3; 2; 1; 0] |> List.map ((+) 1)) = [1 + 3..1 + 0]
[4; 3; 2; 1] = [4..1]
[4; 3; 2; 1] = []
false

unquoteと同様の出力が得られます。 これが本当に欲しかったものかどうかはともかく、たたき台としては十分でしょう。

power assertが必要かどうか

power assertの定義が界隈で共通なのかイマイチわかりませんが、unquoteの出力は簡約結果を表示しているだけなので界隈でよく見られるpower assertではないでしょう。

じゃああのpower assert系の出力が本当に欲しかったものなのかと問われると…どうでしょうね。 あまりpower assertの恩恵にあやかれていない身としては判断が難しいです。 Assertion failedよりは確実に良いことは確かでしょうけれども。

表示については今後のということにしておきます。

Persimmon 2.0.1とPersimmon.MuscleAssert 1.0.0をリリースしました

表題の通りですが、他にもDiff.Match.Patch 2.0.1とFSharp.Object.Diff 1.0.0もリリースしています。

NuGet Gallery | Persimmon 2.0.1

NuGet Gallery | FSharp.Object.Diff 1.0.0

NuGet Gallery | Diff.Match.Patch 2.0.1

NuGet Gallery | Persimmon.MuscleAssert 1.0.0

変更点はPCLや.NET Core対応、機能追加、bug fix、F# 4.1サポート、Visual Studio拡張のためのコードベース変更です。

注意点として、まだVisual Studioのテストエクスプローラー拡張や.NET Core用のAdapterはリリースできていないので実行できない点です。 あくまでライブラリが.NET Coreで実行できるようになったというところに悲しみが漂っていますね…。

本当はそれらも一緒にリリースできれば良かったのですが、謎の挙動によりテストが一部実行できないという状況のためライブラリ群と.NET Framework用コンソールランナーだけを先行リリースすることにしました。

バイナリ互換はありませんがソースコードレベルでの互換性は保っているつもりです。

残りの作業

  • Visual Studioテストエクスプローラー用の拡張
    • 一部テストが実行されない問題を解決する
    • とはいえ私が必要としていないので永遠にリリースされない可能性もあり得る
  • .NET Core用のアダプター
    • モジュール変数として束縛したテストがnullになる現象を解決する(ちなみに関数やメソッド、プロパティとして定義したテストは動く)
    • アセンブリはロードされているみたいでたちが悪い…
  • Persimmon.DriedをPersimmon 2.xベースにする
    • ついでにFsRandomの.NET Core対応をがんばる
    • FsPicklerの.NET Core対応が当分先になりそうなのでMessagePackへの移行も考慮する(できればやりたくないけど)
  • .NET Core SDK向けのテンプレートを作る
  • Xamarin系のランナーを作る
    • iOSAndroidシミュレータ上でテストを実行したい
  • モックライブラリを作る
    • Foqで問題ないけど、なんかこれじゃない感もある
  • Power Assertを作る…?
    • 気が向いたら
    • 最初はたぶんunquoteをラップする方針で作業する
    • なんで作るかと言えば、同値比較以外のアサーションが未だに貧弱だから
  • ドキュメントを増やす
    • 増やせないフラグ
  • プロジェクトサイトを作る
  • ミューテーションテストフレームワーク構想
    • fscxを使えばできそう
  • テストの分散実行
    • Persimmon.Ripeをもうちょっといい感じにしたい
    • TCPソケットでプロセスの通信とか

明らかに1人で作業する分量ではない(確信)。 まぁ、手がまわらないなら切り捨てるか放り投げるだけなので問題ない(?)はず。

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なのにメソッドかよーみたいな気持ちはありつつ、でもまぁそこしかないかみたいな。