Persimmon.Script 2.0.0をリリースした

NuGet Gallery | Persimmon.Script 2.0.0

Persimmon v2を作る過程で「使う人いないし消そう」となってdropしたのですが、最近になってほしいという人が現れたので、v2ようにマイグレーションするくらいなら労力かからないだろうということで復活させました。

まぁ、労力は予想以上に必要でしたが…1日で終わると思ってたら色々試行錯誤したりで3日くらいかかってしまった。

使い方は簡単で

#r @"../packages/examples/Persimmon/lib/net45/Persimmon.dll"
#r @"../packages/examples/Persimmon.Runner/lib/net40/Persimmon.Runner.dll"
#r @"../src/Persimmon.Script/bin/Release/net45/Persimmon.Script.dll"

// このあたりにテスト書く

Script.collectAndRun (fun _ -> Assembly.GetExecutingAssembly())

Script内にテストを書き連ねた後にAssembly.GetExecutingAssembly()スクリプト自身のAssemblyをターゲットとしてテストを収集、実行するだけです。

個人的にはテスト用のdll作ってしまうので使う機会がなさそう…ということもあってメンテナンスを継続するかどうかは期待しないほうが良いです。 あくまでdll作ってテストするまでもないもの(そんなものあるのか…?)に対して使用しましょう。

呪文詠唱!F# #ML_study

ML勉強会 #2 - connpass

発表してきました。

要約

F#のコンピュテーション式を使うと呪文詠唱できます。 呪文詠唱自体に実用性はありませんが、やっていることは応用が効くかもしれません。

はじめに

突然ですが問題です。 

詠唱 {
  モナドは
  単なる
  自己関手の
  圏における
  モノイド対象だよ
  何か問題でも
  ``?``
}

このコードはF#的にvalidでしょうか?

正解

コンピュテーション式用のビルダークラスを適切に準備してあげればvalidになります。

中身

MLStudy2/Program.fs at 78359cf52503daa0c40cf5fed50a60dc89e5c9df · pocketberserker/MLStudy2 · GitHub

要するものは以下のとおり

  • コンパイルできるコードを制限するための型
    • 名前はなんでも良いので、今回はLine0とかつけてます
    • 別に判別共用体でなくても良いですが、シングルトンを作りやすいものが良いでしょう
  • Attributeから値を取得するためのヘルパー関数
    • カスタムオペレーター用のメソッドに指定したAttributeを取得し、そこから情報を抜き出します
  • カスタムオペレーターが定義されたコンピュテーションビルダー
    • カスタムオペレーターを使うことで、任意の文字列をコンピュテーション式のキーワードとして扱える
    • 中身はなんでも良い
    • 今回は呪文詠唱っぽくみえるよう、1秒ごとに呪文を出力している
    • Runコンパイル制限用に使っていた型の値を取り除く

コンピュテーション式は、展開規則に従ってビルダーに存在するメソッド呼び出しに展開されます。 この時、引数や戻り値の型を適切に実装すればあるオペレーターが呼ばれない限りコンパイルエラーとか、指定の順序で呼び出さない限りコンパイルエラーとなるようにできます。

今回のコンピュテーション式は呪文なので、カスタムオペレーターの呼び出し順序は固定です。 呪文は少しでも間違えると発動しない(つまりコンパイルエラー)なのは当然ですよね?

無詠唱

発表中は「無詠唱なんて許さない」と言いましたが、つくれないことはありません。 ビルダーにZeroメソッドを用意することで、無詠唱のようなことが行えます。

詠唱 { () }

https://github.com/pocketberserker/MLStudy2/blob/78359cf52503daa0c40cf5fed50a60dc89e5c9df/src/MLStudy2/Program.fs#L67

Zeroメソッドを定義する際の注意点は以下のとおりです。

  • ビルダークラスに定義されたカスタムオペレーター用のメソッドを全て取得
  • 上記実装はザルに作ったので実行順序が処理系依存ですが、詠唱順序をちゃんと固定したいなら、メソッド名をソートしやすいものにしたほうが無難
  • キーワード名とメソッド名は別でも問題無い
    • デバッグ時にわかりやすくするために名前を合わせることが多いとは思う

コンピュテーション式用のビルダーを生成できないのか?

ファイルから読み込んだ文字列群を用いてTypeProviderでビルダーを生成すれば、任意の詠唱を生成できるのでは、という話をしました。 残念ながら当日には間に合いませんでしたが…。

で、これについて昨日検証してみたところ、現場のコンパイラだと不可能っぽいという結論に至りました。

  • 必要なビルダークラスとメソッド、型までは消去型なら用意できる
    • 生成型は複数の型を生成できる気がしない…
  • 生成したビルダーのカスタムオペレーターがコンパイラにカスタムオペレーターだと認識されない

もしかしてコンパイラのバグだったらどうしよう。

https://github.com/pocketberserker/MLStudy2/blob/288cd0b25fd4cbc28c2dc80439bbc4a6a8a5992f/src/MLStudy2/Program.fs#L90

コードは公開しているので、検証したい方は好きにいじってください。

補足?

Indexed MonadをF#で用意できるのかは不明ですが、やりたいことは同じかと。 今回は時間がなかったのでごり押しでコードを書いてしまいました。

まとめ

言語機能を使って遊ぶのはとても楽しいです。

Microsoft MVP for Visual Studio and Development Technologies(F#)を再受賞しました

Twitterに先んじて投稿していましたが一応こちらでも。 昨年に引き続きVisual Studio and Development Technologiesです。 長いですがようするにF#です。 日本で F# のみの活動でこのカテゴリの人、現状だと多く見積もって3人くらいな気がします。

昨年度は何かを作ったりひたすら.NET Coreと触れ合う一年だった気がします。 たぶん.NET Core 2.0でまた色々あるはずなのでこのあたりは引き続き追っていきたいところですね。

XamarinやUnityは…今年こそ手を出したいですね、Unity 2017とか面白そうですし。

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

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人で作業する分量ではない(確信)。 まぁ、手がまわらないなら切り捨てるか放り投げるだけなので問題ない(?)はず。