MessagePack.FSharpExtensions 2.0.0をリリースしました

www.nuget.org

全国約…何人かわからない F#erの皆様、お待たせしました(お待たせしすぎたかもしれません)。

MessagePack C# v2が正式リリースされてから6か月…さすがにまずいと思ったので重い腰をあげました。 実際は眠れない怒りをぶつけただけなのですけれども。 おそらくこの記事が予約投稿されたときには眠りについているはず。

こういう話はあるものの、自分は特に更新を急いでないからなぁ…と別件にずっとかまけててごめんなさい。 今回の作業は半日かかっていないので、v2がリリースされたときにやっておくべきでした…。

何が変わったのか

作業diff: https://github.com/pocketberserker/MessagePack.FSharpExtensions/pull/4

古い環境を窓から投げ捨てる勢いで書き換えました。

動作やAPIは特に変えてません。 とはいえ、大元のMessagePack C# v2が結構変わっているので、MessagePack.FSharpExtensions v1を使っている人(いるのか!?)は書き換えが必要です。 inref&がでてくるあたり、最近の F# っぽい(?)

他のライブラリと異なり、内部APIを引っ張ってきたりしつつILを直書きしている関係で、メンテナンスに気力が必要なんですよね。 判別共用体用のFormatterを生成するDiscriminatedUnionResolverDynamicObjectResolverDynamicUnionResolver悪魔合体させたような存在なので、元ネタがある分楽…ということもなく、同じResolverでStructも捌けるようにしないといけないので注意していないとTypeInitializationErrorの刑に処されます。つらい。

そのかわり、本家と違ってUnityを気にしなくていいのでその分は楽です(その環境が良いかどうかは別の話)。

追加機能をいれるか悩みましたが、何をやるにしても頑張りが必要そうだったので見送ってます。

.NET シリアライズ最前線に追い付けたので一安心。 置いていかれないように引き続きがんば…がん…ががが…

NaNとMap

先日、JavaScriptで以下の挙動になるのはなんでだろうねという話になった。

> m=new Map();
Map {}
> m.set(Number.NaN, 0)
Map { NaN => 0 }
> m.set(Number.NaN, 1)
Map { NaN => 1 }
> m.set(Number.NaN, 2)
Map { NaN => 2 }
> Number.NaN === Number.NaN
false
> Number.NaN !== Number.NaN
true

そして理由はMDNに書かれていた。

厳格な等価性では NaN を他のどの値 (自分自身も含む) とも等しくないものとして扱います

https://developer.mozilla.org/ja/docs/Web/JavaScript/Equality_comparisons_and_sameness

NaN は NaN と同じとみなされ (NaN !== NaN であっても)、他の値はすべて === 演算子の意味に従って等価性が考慮されます

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map

めでたしめでたし…いや、もうちょっとだけ続くんじゃよ。

.NETのDictionaryとF#のMap

ふと、ほかの…例えば.NETのDictionaryやF#のMapはどうなるのか気になったので、Try F#上で試してみた。

open System.Collections.Generic

// nanの定義
// https://github.com/dotnet/fsharp/blob/6a8885ff1152db81ab37b94f048a01e88b8847d6/src/fsharp/FSharp.Core/prim-types.fs#L3758

Map.empty
|> Map.add nan 1
|> Map.add nan 2
|> printfn "%A"

let d = Dictionary<float, int>()
d.Add(nan, 1)
d.Add(nan, 2)
d |> Seq.iter (fun kv -> printfn "%A" kv)

nan = nan |> printfn "%b"
nan <> nan |> printfn "%b"
map [(NaN, 1); (NaN, 2)]
NaN,2
false
true

もうちょっと深堀りする必要がありそうだ。

Dictionary

DictionarySystem.IEquatable<T>が実装されていればそれを使うことになっている。

F#におけるfloatは.NETでいうところのdoubleだ。 DoubleはIEquatable<double>が実装されており、NaN == NaNはtrueとなるよう実装されている。よってDictionaryでは値が上書きされる。

https://github.com/microsoft/referencesource/blob/a7bd3242bd7732dec4aebb21fbc0f6de61c2545e/mscorlib/system/double.cs#L147

https://github.com/dotnet/runtime/blob/221f869e9bac3cafcfe6bd35d062e2fbfe8accba/src/libraries/System.Runtime/tests/System/DoubleTests.cs#L111

F# Map

F#のMapのkeyはIComparer<'T>を使って比較される。

https://github.com/dotnet/fsharp/blob/6a8885ff1152db81ab37b94f048a01e88b8847d6/src/fsharp/FSharp.Core/map.fs#L123

今回はMap.emptyに要素を追加していったので、Map.emptyが用意したLanguagePrimitives.FastGenericComparer<'T>がcomparerとして使われる。

https://github.com/dotnet/fsharp/blob/6a8885ff1152db81ab37b94f048a01e88b8847d6/src/fsharp/FSharp.Core/map.fs#L466

もっと追ってみる。

https://github.com/dotnet/fsharp/blob/78bb83aca6cba3d85dee111211d4c0c99a37595d/src/fsharp/FSharp.Core/prim-types.fs#L2177

https://github.com/dotnet/fsharp/blob/78bb83aca6cba3d85dee111211d4c0c99a37595d/src/fsharp/FSharp.Core/prim-types.fs#L2088

GenericComparisonを使っている関数は他にcompareがあるので、こいつで何を返しているか見てみよう。

https://github.com/dotnet/fsharp/blob/78bb83aca6cba3d85dee111211d4c0c99a37595d/src/fsharp/FSharp.Core/prim-types.fs#L3293

compare nan nan |> printfn "%d"
1

ということで、別のkey扱いである。

もっと実装を追いたいなら以下を読み進めていけばよいはず。

https://github.com/dotnet/fsharp/blob/78bb83aca6cba3d85dee111211d4c0c99a37595d/src/fsharp/FSharp.Core/prim-types.fs#L2159

https://github.com/dotnet/fsharp/blob/78bb83aca6cba3d85dee111211d4c0c99a37595d/src/fsharp/FSharp.Core/prim-types.fs#L1954

https://github.com/dotnet/fsharp/blob/78bb83aca6cba3d85dee111211d4c0c99a37595d/src/fsharp/FSharp.Core/prim-types.fs#L1097

https://github.com/dotnet/fsharp/blob/78bb83aca6cba3d85dee111211d4c0c99a37595d/src/fsharp/FSharp.Core/prim-types.fs#L1080

ちなみに、NaNだとkeyが一致しないので値は取得できない。

let d =
  Map.empty
  |> Map.add nan 1
  |> Map.add nan 2

// Noneになる
d
|> Map.tryFind nan
|> printfn "%A"

TryFSharpとその技術スタック

今は昔、Web上で簡単にF#を試せるTry FSharpはSilverlight上で動作していました。 しかし世界から光が失われた結果、賢者たちは世界の再構築を余儀なくされたのでした。

Try F#

というわけでTry FSharpは今もFSharp Software Foundation下で開発、運営されていてしかもモダンなもので構築されているよという話です。 どのくらいモダンかといえば

聞き覚えのあるものばかりだと思います。 これくらい道具がそろっているとそこそこのウェブサイトであればフルF#で開発できるかも。 いい時代になりましたね。

ちなみにこのTry FSharpはソースコードが公開されています。 興味のある方はぜひ眺めてみましょう。

GitHub - fsharp/TryFSharp

FableやElmishの話を書き始めるとちょいと長くなりそうなので、この記事では触れずに終わります。 中身がない記事でごめんね、Try FSharpを紹介したかっただけなのさ!

ところで、某FSDNをフル F# でリプレースしようと画策しているのですが、果たしてうまくいくのでしょうか…? うまくいくといいなぁ(未来の自分に期待、そしてフラグ)

Microsoft MVP for Developer Technologiesを再受賞しました

今年も受賞できました7回目。関係各位の皆様ありがとうございます(特に前職ドワンゴ)。 ちなみに今年も活動報告は F# オンリーでした。

ここ数年は F# の新機能が小粒揃いだし、仕事は相変わらず .NET 関係ないしで悶々としていましたが、ようやくメジャーバージョンアップの足音が聞こえてきたのでまた1年頑張っていこうと思います。 リポジトリ.NET Foundation下に移ったことですし、気持ちを新たにやっていく所存です。

まぁ、そのまえにまずはFSDNの全面改修頑張りますはい。。。

というわけで、今後ともよろしくお願いします。

F# 4.6の話

忙しくて書く余裕がなかったF# 4.6についてです。といっても機能追加は小粒なのでさくっといきましょう。 間違ってたら指摘をお願いします。

ちなみにこの記事を書いている時点における最新の FSharp.Core は 4.6.2です。

FS-1030

https://github.com/fsharp/fslang-design/blob/7240c64de3e85f69de872b1f0a7db4d91f7a7e1a/FSharp-4.6/FS-1030-anonymous-records.md

anonymous recordsはC#に存在するanonymous objectsのレコード版ですね。 これまでASP.NET CoreなどのC#フレームワークをF#で触ったりするとanonymous objectsが前提となっていた部分が煩雑でしたが、これでいい感じに書けるようになります。

FS-1065

https://github.com/fsharp/fslang-design/blob/7240c64de3e85f69de872b1f0a7db4d91f7a7e1a/FSharp.Core-4.6.0/FS-1065-valueoption-parity.md

4.5で導入されたvalue option用の関数やメソッドが追加されます(なぜ4.5で入らなかったのと突っ込んではいけない)。 あとDebuggerDisplay属性も付与されるようです。

FS-1066

https://github.com/fsharp/fslang-design/blob/7240c64de3e85f69de872b1f0a7db4d91f7a7e1a/FSharp.Core-4.6.0/FS-1066-tryExactlyOne.md

array, seq, listにtryExactlyOne関数が追加されます。 コレクション中に要素が1つしかなかった場合にその要素をとりだすexactlyOne関数のoption版ですね。

おわりに

マイナーバージョンアップなのでこんなものでしょうと思うべきか、もうちょっと頑張ってほしいと思うべきか…それはともかくとして、ちゃんと改善はされていってるのは良い事だでしょう。 RFCsフォルダに未だリリースされていない仕様がたまってきているのでそろそろリリースされないかなぁ。

gRPC C# のHealthCheck

この記事は.NET, .NET Core, monoのランタイム・フレームワーク・ライブラリ Advent Calendar 2018 - Qiita 25日目の記事です。 ほんとはGiraffeの話を書こうと思っていたけど、仕込む時間がなかったので別ネタで行きます…!

あと時間がないので色々と飛ばして書きます。

gRPCのhealth checkプロトコル

https://github.com/grpc/grpc/blob/v1.17.2/doc/health-checking.md にあるとおり、gRPCにはhealth checkプロトコルが用意されています。 これを使って例えば:

  • サーバー側でヘルスチェックに応答できるようにしておく
  • クライアントにhealthcheckコマンドを用意する
  • KubernetesのLiveness Probeでhealth checkコマンドを叩く

とかできたりします。

ライブラリインストール

公式が提供しているライブラリを使います。

https://www.nuget.org/packages/Grpc.HealthCheck/

サーバー側

使う名前空間は以下:

using Grpc.Core;
using Grpc.Health.V1;
using Grpc.HealthCheck;

他のサービスと同様にHealthServiceをサーバーに登録し、サーバー起動後にSetStatusで状態を設定してあげます。 第1引数はサービス名でいいはず。

var health = new HealthServiceImpl();
var server = new Server
{
    Services = {
        Health.BindService(health)
    },
    Ports = { new ServerPort("localhost", 50051, ServerCredentials.Insecure) }
};
server.Start();
health.SetStatus("greeter", HealthCheckResponse.Types.ServingStatus.Serving);

これで、クライアントから起動しているか確認できるようにはるはず。

クライアント側

使う名前空間は以下:

using Grpc.Core;
using Grpc.Health.V1;

クライアントはレスポンスから状態を取得して分岐させます。

Channel channel = new Channel("localhost:50051", ChannelCredentials.Insecure);
try
{
    var client = new Health.HealthClient(channel);
    var response = client.Check(new HealthCheckRequest { Service = "fanfun" });
    switch (response.Status)
    {
        case HealthCheckResponse.Types.ServingStatus.Serving:
            // 何か処理
            return;
        case HealthCheckResponse.Types.ServingStatus.NotServing:
            // 何か処理
            return;
        default:
            // v1だと他の状態はUnknownのみ
            // 何か処理
            return;
    }
} catch(Exception e) {
    // 略
}

あとは実際にサーバーを起動しつつクライアントを実行すれば結果が確認できるはず。

ところで

MagicOnionにはPingとHeartbeatサービスが組み込みで用意されています(このあたり)。 これ以外にも便利な機能があるので C# に統一できるならこっちのほうがいいかもしれません。

私は grpc-web とセットで使いたいのでMagicOnionは使えないですけどね!

commandpostのBuckleScript bindingを書いた

これは元ドワンゴ Advent Calendarの22日目の記事です……遅刻しましたけど22日ということで。

今年に入ってから、私はBuckleScriptで自作ノベルゲームエンジン用のトランスパイラhttps://github.com/cowlick/cowlick-sexpr-compilerを開発しています(最近さぼってたけどぼちぼち再開予定)。 このツールを開発する際に、option parserとしてこれまでは https://github.com/jaredly/minimist.reを使用していました。 しかし、このライブラリには同じオプション名が複数回指定されると配列で受け取る、といったAPIが用意されていないので最近機能追加をしようとしたタイミングで困ってしまいました。

さて、では他のライブラリを……使おうにも選択肢がないです。 このあたりにユーザー数の問題が現れますねぇ。

ではしょうがないので自作か、という話になりますが、それよりは既存のライブラリをbindingしてしまえという話に落ち着くので書いてしましましょう。 今回は個人的によく使うhttps://github.com/vvakame/commandpostを選択しました。

成果物

publish済みです。

https://www.npmjs.com/package/@pocketberserker/bs-commandpost https://github.com/pocketberserker/bs-commandpost

方針

今回はCommandクラスとcreate関数、exec関数に型をつけました。 ArgumentOptionは使う機会がなさそうなのでひとまず後回しにしています。

Commandクラスのラップ方針はexternalbs.sendを使ってモジュールに関数を定義する形にしました。 Command型は('a, 'b) tで定義し、第1引数にはこのオブジェクトを渡すようにします。 例えば、create関数とdescriptionメソッドの定義はこんな感じになります。

module Command = struct
  type ('a, 'b) t

  external description: ('a, 'b) t -> string -> ('a, 'b) t = "" [@@bs.send]
  (* 色々略 *)
end

external create: string -> ('a, 'b) Command.t = "Command" [@@bs.new] [@@bs.module "commandpost"]

bs.newはコンストラクタ呼び出しであることを示します。 bs.moduleを指定するとその関数を呼び出すときに記述したモジュールがrequireされます。

BuckleScriptは|.で第1引数をパイプラインの対象にできるので、第1引数を対象のオブジェクトにしていてもそんなに困ることはないです。

create "test"
|. Command.description "test"

パイプライン便利。

型の話

悩んだ点がふたつあります。 ひとつはコンストラクタの型パラメータCommand<Opt, Arg>、もうひとつはanyです。

型パラメータは、今回に関してはletで束縛する際に明示して型をあわせる方向でなんとかしました。 これがあっているのかはちょっとわかりませんが…。

type root_options = RootOptions
type root_args = RootArgs

let root: (root_options, root_args) Command.t = create "root"

anyについてはてきとーに型があえばいいので使ってない型パラメータを指定する方向で調整しました。

まとめ

BuckleScriptでは比較的簡単にbindingが書けます。 というわけで皆さんもbindingに挑戦してみましょう。