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

この記事は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# 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# 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以下を窓から投げ捨てる覚悟をしても良いかもしれませんね?

FSharp.Control.ImperativeAsyncの紹介

この記事はF# Advent Calendar 2016の8日目のものです。 大遅刻しました…orz

qiita.com

今回はFsharp Bootcamp Tokyo 2016 with Tomas Petricekで議論した結果生まれたライブラリの紹介です。

https://github.com/pocketberserker/FSharp.Control.ImperativeAsync

Asyncでもreturnで大域脱出できるようになるよ、という代物です。

このライブラリではImperativeAsyncという型を用意していますが、ユーザがこの型を明示的に生成することはできません。 あくまで制御用の型です。 中身はAsync<'T option>をラップしただけのものですが、別名をつけておいた方がわかりやすいのでこうしました。

とまぁ、こんな感じで皆さんも気軽にコンピュテーション式を提供するだけのライブラリを作ってみてはいかがでしょうか?

.NET Core用のテストランナーを作る

[2017/02/18追記].NET Core SDK RC以降で実装方法が変わったのでこの記事を読むべきではありません

これは.NET Core Advent Calendar 4日目の記事です。

qiita.com

.NET Coreに対応したユニットテスト

さて、世の中にはすでに.NET Coreに対応済みのテスティングフレームワークがいくつか存在します。

ここで、.NET Core CLI用のランナーを提供しているプロジェクトをみてみましょう。

主要フレームワークはすでにpreviewやbeta版がリリースされているようですね。 Visual Studioにべったり(?)だったMSTestも次のバージョンからNuGetで取得できるようになったのが見所かもしれません。

ちなみに、私もF#向けテスティングフレームワーク開発に携わっている一人としてrunnerを作っていたりします。

今回はこういったランナーを実装するにはどうすれば良いか、という話を雑に書いていきます。

注意事項

  • 結局個人メモみたいな形になってしまったのでわかりづらいかも…
  • 2016/12/04時点での情報なので、正式リリースの際には仕様が変わっているかもしれないことに注意してください。
  • コンソールのみで動くランナーを作る場合にはここまでの知識は必要ありません
    • 逆を言えば、Visual Studio上でテストを動かしたいなら対応必須です

test communication protocol

.NET Core CLIにはtest communication protocolというテスト用のプロトコルが定められています。 Visual Studio 2017以降のテストエクスローラーはこのプロトコルを使って.NET Coreなテストを実行しているようですね。

.NET Core CLI test communication protocol | Microsoft Docs

機械翻訳?もあるみたいです。

.NET Core CLI テスト通信プロトコル | Microsoft Docs

必要になる知識はこれを読めば全部手に入るのですが、これを読んだだけで実装できたら苦労はしない…。

というわけで、以降では実装に必要なものなどをあげていきます。

パッケージ名

パッケージ名に関してはproject.json時代の話を書くので、状況次第では役に立たなくなるかもしれません。

パッケージ名はdotnet-test-foofoo部分にプロジェクト名をつけます。 これはproject.json"testRunner": "foo"と記述すると、.NET CLIが依存関係の中からdotnet-test-fooパッケージを探索するという決まり事があるためです。 先に紹介したドキュメントのシーケンス図内ではdotnet-test-runnerという表現がなされています。

Microsoft.Extensions.Testing.Abstractions

作成したプロジェクトの依存関係に次のパッケージを追加します。

www.nuget.org

このパッケージにはAdapterやdotnet testとの通信で使う型やインターフェースが定義されています。

コマンドラインオプション

ここに列挙するオプションはAdapter, dotnet test, ランナーが連携する際に必要となります。

Test discovery(テスト探索)とTest execution(テスト実行)に共通するオプションとそうでないオプションがあります。

共通

designtimeオプション

design modeを示すオプションです。 このオプションがついている場合はコンソール実行ではないと考えたほうが良いでしょう。

--designtime

portオプション

Adapterとランナー、あるいはdotnet testとランナー間でTCP通信するためのポート番号がオプションで渡されます。

--port ポート番号

テスト対象のアセンブリ一覧

テスト対象のアセンブリはオプション名なしのスペース区切りで渡されます。

testAssembly1 testAssembly2

Test discoveryで必要になるオプション

listオプション

テストのリストアップを要求するオプションです。 このオプションが渡された場合、ランナーはテストを実行する必要はありません。

--list

Test executionで必要になるオプション

wait-commandオプション

実行するテスト一覧を取得するために待機する必要があることを示すオプションです。

--wait-command

実装面

探索したテストの送信やテスト結果の送信はITestDiscoverySinkもしくはITestExecutionSinkを実装したクラスを使って行います。 この2つのインターフェースはMicrosoft.Extensions.Testing.Abstractionsにあります。

ポートが指定されていない場合

Microsoft.Extensions.Testing.Abstractionsに用意されているStreamingTestDiscoverySinkとStreamingTestExecutionSinkを使うのが手っ取り早いです。 おそらくコンソール上で実行しているはずなので、Console.OpenStandardOutputを渡せばよいと思います。

ポートが指定されている場合

独自にITestDiscoverySink, ITestExecutionSinkを実装したクラスを用意します……といっても基本はdotnet-test-nunitのやつをライセンスつけて持って来ればよいと思います。 NUnitのやつはSocket, NetworkStream, BinaryWriterを使って通信するようになっています。 IPアドレスは自分のPCがターゲットなのでIPAddress.Loopback固定で大丈夫なはずです。

Adapterやdotnet testとの通信はMessage型を使ってやりとりされます。 そして、このMessage型のPayloadはNewtonsoft.Jsonを使っているので、送信時は必然的にNewtonsoft.Jsonを使ってシリアライズしてから送信します。

wait-commandが指定された場合

  1. ITestExecutionSink#SendWaitingCommandで待機中であることを送信する
  2. Message#MessageTypeに指定する文字列はTestRunner.WaitingCommand
  3. BinaryReader等で送られてきたJSONをMessageとしてデシリアライズ
  4. ここで送られてくるMessage#Payloadの型はRunTestsMessageなので、message.Payload.ToObject<RunTestsMessage>().Testsなどとすれば実行すべきテスト一覧が取得できる

ここで取得できるテスト一覧はFullyQualifiedNameとなっています。 これを用いてテスト実行前にフィルタリングしていくことになります。

回りくどいなーという気持ちになるかもしれませんが、FullyQualifiedName自体が長かったりそもそもテスト数が多かったりしてコマンドラインのサイズ上限を容易に超えてしまうので仕方がないですね。

メッセージの送信

Adapterやdotnet testに情報を送信する際にもMessage型を使います。

MessageはMessageTypeというstringのプロパティを持ちます。 で、このMessageTypeに指定する文字列は決まっているわけですが、定数として定義されているわけではないのでランナー側で各自定義するしかないようです。 シリアライズ/デシリアライズのことを考えてstringなのはまぁ許せるとして、どうして定数をパッケージ側で用意しなかったのでしょうね…。

TestFound

--listオプションが指定されている時にテストを発見した場合はITestDiscoverySink#SendTestFoundを使ってテストを送信します。 Message#MessageTypeに指定する文字列はTestDiscovery.TestFoundです。

SendTestFoundや後述するSendTestStartedではTest型のオブジェクトを送信する必要があるのですが、Test#Propertiesのvalueにシリアライズできないような値を突っ込むと落ちるので気をつけましょう。

TestStarted

--designtimeオプションが指定されている時、テスト実行前にITestExecutionSink#SendTestStartedを使ってテスト開始を通知します。 Message#MessageTypeに指定する文字列はTestExecution.TestStartedです。

TestResult

--designtimeオプションが指定されている時、テスト実行後にITestExecutionSink#SendTestResultを使ってテスト結果を報告します。 Message#MessageTypeに指定する文字列はTestExecution.TestResultです。

なお、F#のAsyncを使ってParallelに実行しつつITestExecutionSink#SendTestResultを呼び出したところ、テストを2つ以上実行しようとした際にSocketExceptionが投げられました。 同期的な通知に修正したらこの例外は発生しなくなったので、もしかしたら2016/12/04時点ではITestExecutionSink#SendTestResultを同期的に呼び出す必要があるかもしれません(イマイチ確証が持てないですが…)。

後片付け

--designtimeが指定されている場合は、ITestDiscoverySink#SendTestCompletedITestExecutionSink#SendTestComletedを使って完了を通知します。 Message#MessageTypeに指定する文字列はTestRunner.TestCompletedです。

コンソールで実行している場合は別途コンソール用の出力を実装したり、XMLレポートをちゅつ力してあげれば良いでしょう。

おわりに

テストランナーを実装するには色々とお作法に従う必要があることが理解できたかと思います。

しかし、Visual Studioのテストエクスプローラー用拡張を実装するよりは格段に楽になっています。 VS拡張ではドキュメントがほとんどなく、Microsoft.VisualStudio.TestPlatform.ObjectModelというNuGetにpublishされていないdllと格闘する必要があったためです。

今回言及していない点としては、AppDomainどうするの問題があります。 現行の.NET CoreにはAppDomainに相当する機能がないため、どうすれば良いのかよくわからないというのが正直なところです。 xUnit.NETではAppDomainがある環境とない環境でifdefを駆使してるみたいですが、果たしてこれが正解なのか…?

とまぁ、長くなりましたが、実装するかどうかはともかく自分たちが使っているプロトコルの仕様を読んでみるのも一興かと思います。

scalaでmuscle assert的なライブラリの試作

Scaladogというテスティングフレームワークを以前から作ってみているわけですが、こいつでPersimmon.MuscleAssertみたいなことはできるのかなと思い作……ろうとして放置していたものを少し手直しして動くようにしました。

https://github.com/scala-kennel/hound

Diffの実装自体はただのshapelessのようだ。

初期はhttps://github.com/xdotai/diffがよさそうに見えたのでこのライブラリを使っていたのですが、所々使いづらかったりあまり更新頻度高くなさそうだったりScala.js対応されそうになかったりしたので「Diffくらい作れるでしょ」という軽い気持ちで自作に走りました。

なお実装が雑なのでcase classの型が異なる時に型名しか表示されないです。 あと、shapelessを使いサボった関係で、そもそもprimitive type + case classくらいしか表示できない問題があります……Scalaでリフレクションを頑張る体力がないです……。

Stringの個別diffはライブラリがぱっと探せなかったので非対応です。 よさげなライブラリがあれば教えて下さい(Javaのものでも構いません)。

assertion時にオブジェクトのdiffを取得するのは、たぶん有名どころのテスティングフレームワークでも実装できると思います。 が、まぁ需要はなさそうですし私は作る予定はないです。 ScalaTestにはpower assertあるので、基本はそっちで満足する人が多いんじゃないかなぁと。

対象をJSONに特化させるならdiffson等を使って実装する、という手もあります。

最小のコンピュテーション式

メモ。

使う規則

T(e;, V, C, q) = C(e;b.Zero())

この規則がvalidなコンピュテーション式を作れるはず。 Zeroメソッドのみを用意すれば良いのでBuilderの実装も最小限なはず?*1

コード

// 定義
type A() = member x.Zero()=()
let a = A()

// 実際に試す
a { () }

F# 4.0のfsiで確認。

  • ビルダーのクラス名に制限はない
  • 束縛名にも制限はない

なので一文字でも問題なく判別可能です。

公開後の追記

単にビルダーインスタンスを返すだけなのでこっちが正しいですね。 自分でxにしておきながら気付いてなかったです…失礼しました。

zeclさん、指摘ありがとうございました。

番外編

バッククオート2つで囲めばIdentifierにできるので、(たぶん)絵文字も可能です。

type ``🍣``() = member x.Zero()=()
let ``🍣`` = ``🍣``()

``🍣`` { () }

表示されない場合は環境の問題でしょう。

*1:と思っていたらミスしてました。追記部分を読んでください