SpanJson用の F# 拡張ライブラリを作った

SpanJson という新手のJSONリアライザが今年になって登場していたので、調査がてらに F# 拡張を作りました。

https://github.com/pocketberserker/SpanJson.FSharp.Formatter

https://www.nuget.org/packages/SpanJson.FSharp.Formatter/0.1.0

SpanJson自体が.NETCoreApp 2.1でしか使えない上に F# 向けライブラリなので使う人皆無な気がしていますが、SpanJsonに詳しくなったのでよしとしましょう。

0.1.0で対応しているのは以下の型です。

  • option
  • voption
  • list
  • map
  • record
  • struct record

setは既存実装のままでシリアライズ、デシリアライズできるので自前で用意しませんでした。 判別共用体は例によって必要性がまだわかないので未対応です。 あとはvoption対応のために FSharp.Core を 4.5以上に強制しています、各位新しいバージョンを使っていきましょう。

SpanJsonはUtf8Jsonと似ているようでところどころ違うのが面白いですね、個人的にはUtf8JsonのAPIのほうが好み(というか単純に実装しやすい)と思いましたが。 Resolverの伝搬を型パラメータで行うの結構大変な気がするのですがそこらへんどうなんでしょう…?

あとはSpanJsonでクラスや構造体をシリアライズ、デシリアライズする際はデフォルトかattributeを付けたコンストラクタでなければならない縛りはちょっと地味に厳しいかも? これの関係でrecordはシリアライズできるけどデシリアライズできないという状態になったので、外部ライブラリの型をシリアライズしようとするときに色々書く羽目になるかもしれません。

そういえばこのライブラリを開発する際に C# 7.3 を使ったのですがout varとか便利ですね…という気持ちになりました。

Akashic Engineでガチャが回せるゲームのサンプルを作ってみた

とある個人的な(?)イベントのためにガチャつきアドベンチャーゲームもどきを実装しよう、と思い至ったので作ってみましたというメモ書きです。 下記はその残滓(公開できない & したくないものを排除しシナリオを簡易化したもの)。

https://github.com/pocketberserker/rgo

ちょっとしたシナリオとチュートリアルガチャと本番ガチャをまわすだけのゲーム(?)です。 画像とフォントを揃えれば試せると思います。 ライセンスはちょっと決めかねているのですがそのうち…。

使ったもの

Akashic Engineについては下記記事を一読ください。

https://dwango.github.io/articles/akashic/

ノベルゲームエンジンについては昨年末にQiitaに記事を書きました。

https://qiita.com/pocketberserker/items/a14c4358adff46ee9742

あとは抽選にwalkers alias methodを使おうと思い、どうせだからAkashic Engine向けライブラリにしておこうと考えたので(誰が使うんだ?)作って組み込んだ感じです。

https://github.com/pocketberserker/akashic-random

今回のゲームはクライアントのみの実装です。 サーバーも実装しようかと企んでいたのですが、猶予が3週間弱くらいしかなかったので泣く泣く断念……とはいえ当日のイベントでは笑ってもらえたのでなんとかなってよかった。

追加したかったけど無理だったもの

  • n連の実装
    • 演出の都合により途中であきらめた
  • キャラクター情報閲覧機能
    • UI作る余裕が…

空き時間をみつけて実装したいところですはい。

F# 向けテスティングフレームワークPersimmonのv4系リリース

だいたい作業し終えたので告知です。

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

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

https://www.nuget.org/packages/Persimmon.Dried/4.0.1

https://www.nuget.org/packages/Persimmon.MuscleAssert/3.0.0

https://www.nuget.org/packages/Persimmon.Script/4.0.0

https://www.nuget.org/packages/Persimmon.Unquote/0.2.0

paket.templateに慣れていなくてpatch versionが少しだけ上がってます…。

例外として、PersimmockはPersimmon非依存なので特にアップデートはありません。

Fake.Persimmonについては後述します。

netstandard1.6 or netstandard2.0サポート

上記で紹介したライブラリはどちらかに対応しています。 ようやくスタートラインに立てた…。

FSharp.Core 4.5.xが利用可能

ユーザ側はFSharp.Coreのバージョンを気にしなくて良くなったので*1新しめのFSharp.Coreでも動くはずです。

これはだいたい id:htid46 さんのおかげです、ありがとうございました。

net40以下 & PCLドロップ

FSharp.Core 4.2.x以降サポートされなくなったframeworkをサポートから外しました。 もしこれらの環境で使いたい場合はPersimmon 3系以前を使ってください。

Fake.Persimmon

FAKE 5で命名規約がかなり変わったこともあってdeprecatedです。

もし使いたいなら id:htid46 さんがgistに試作品を用意してくださったのでこれを使ってください。

https://gist.github.com/hafuu/ea5b0140d01beb97f10ed20079cdcf72

今後の予定

ドキュメントが散らばりがちなのでgithub.ioに作る予定です。 というかFSharp.Formattingがもう厳しい…。

あとPersimmon.Templatesはそのうち更新します。

*1:今まで無理だったのかよ、というツッコミは…ごめんなさい

F# 4.5の話

気が付いたら nugetにFSharp.Core 4.5.0がリリースされていたので、4.5について雑多に書きなぐります。

Versioning

F# はこれまで 4.0から4.1へとマイナーバージョンをあげていました。 ところが今回はF# 4.5、間の3つをスキップしています。 スキップした理由は fslang-designで確認できます。

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/tooling/FST-1004-versioning-plan.md

There will be no F# 4.2, F# 4.3, or F# 4.4 language version - it will skip to F# 4.5. This is because it will allow versions of the F# language, FSharp.Core, and FSharp.Core NuGet package to line up in a VS 15.7 timeframe.

(F# では言語、FSharp.Core(assembly)、FSharp.Core nugetパッケージという3つのバージョンを知っておく必要があります)

新機能

F# 4.5とFSharp.Core 4.5.0に追加された機能はRFCで詳細を確認できます。

言語: https://github.com/fsharp/fslang-design/tree/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp-4.5

ライブラリ: https://github.com/fsharp/fslang-design/tree/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp.Core-4.5.0.0

言語的な意味でのF# 4.5はVS 15.8を待つことになりますが、FSharp.Coreのほうは既に使えるので試してみると良いかと。

まぁ、それだけでこの記事終わりは味気ないのでかいつまんで紹介。

RFC FS-1037

https://github.com/fsharp/fslang-design/blob/master/FSharp-4.5/FS-1037-subsumption-for-union-constructors.md

かつてのバージョンでは判別共用体のコンストラクタとパイプライン演算子の組み合わせによってはコンパイルに失敗します(パイプラインを使わない場合は通る)が、4.5からは通るようになるという話。

RFC FS-1047

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp-4.5/FS-1047-match-bang.md

コンピュテーション式にmatch!が追加されます。 コンパイル時にlet!matchに展開されるのでバイナリ互換は保たれるはず。

もちろんエディタやコンパイラはF# 4.5以降でないと認識できない。

RFC FS-1053

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp-4.5/FS-1053-span.md

.NETの新機能としてSpanが追加されたので、F# でもサポート。 色々と型が追加されているけれど紹介が大変面倒なのでそのうち別記事を書きます。

RFC FS-1054

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp-4.5/FS-1054-undent-list-args.md

リストや配列リテラルのインデントルールが緩和されるらしい。

"undentation" (i.e. relaxations to the indentation rules)

という造語(?)の説明を読み飛ばすとひどい目にあう。

RFC FS-1055

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp-4.5/FS-1055-subsumption-for-yield-in-sequence-expression.md

リストや配列リテラルではyieldが使えるのですが、型によってはupcastを明示する必要がありました。 このupcastが不要になるよという話。

RFC FS-1057

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp-4.5/FS-1058-make-enum-cases-public.md

F# におけるenum生成の変更。

RFC FS-1045

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp.Core-4.5.0.0/FS-1045-func-to-fsharpfunc-overloads.md

Add new FuncConvert.FromFunc and FuncConvert.FromAction APIs.

RFC FS-1050

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp.Core-4.5.0.0/FS-1050-trygetvalue-map.md

MapTryGetValueが追加されました。 値返却時にallocationが不要な分performance-friendlyらしい。

RFC FS-1039

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp.Core-4.5.0.0/FS-1057-valueoption.md

sturctなoptionであるValueOption型と、voptionというtype aliasが追加されました。

FS-1058

https://github.com/fsharp/fslang-design/blob/69c7c47931f8205c1cdf28d5819d675de734bd8e/FSharp.Core-4.5.0.0/FS-1059-improve-async-stack-traces.md

asyncのstack traceが改善されたらしいです。 詳しくは上記リンクを読んでください。

Microsoft MVP for Visual Studio and Development Technologiesを再受賞しました

今年もなんとか受賞できました、6年連続とは我ながらびっくりですね。 関係各位ありがとうございます。

Visual Studio and Development Technologiesと言っても相変わらず F# が主戦場です。 とはいえ最近は.NET Core対応云々の話が多かったかもしれません。 あとはILやコンパイラの知識が地味に増えたので手を広げられる範囲が少し増えたかも…? なんにせよ引き続きインプットとアウトプットですね。

OSSは相変わらずメンテしているようなしていないような…反応が遅くなってしまい申し訳なさががが。 重い腰を上げるスピードを速める手段を確立したいところです。 雑に作るのはできるんですけどね…難しい。

ところで最近はもう少し F# に関するアウトプットを増やしたいなと思いつつ、とはいえプライベートのみでの活動にも限界を感じつつ、そのプライベートもあれやりたいこれ試したいと発散気味なのでどうにかしないと…うーん時間が足りない。 あと半年くらいで三十路に突入するのもあっていろいろ悩みつつ、まぁ何かしら調査や試作や実装やメンテナンスをやっていく所存です。

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

進捗大陸03に参加しました

booth.pm

紹介記事を書こう書こうと思っていたら、気がつけば技術書典04から数日すぎてました。

どういう流れで進捗大陸に拾っていただいたかとか、中身についてはno-maddoさんの記事をご覧ください。

私の記事ではBuckleScriptでなんちゃってaltJSを作る方法について書きました。 サンプルコードはGitHubで公開しています。

https://github.com/pocketberserker/shinchoku-tairiku-03-bucklescript

このレベルなら字句解析や構文解析はいらないでしょ、という話もあるかもしれませんが、規模の大きな言語でもこのあたりの設定はかわらないと思うので参考にどうぞ。

他の記事はぜひ購入して中身を確認していただけると…!

F# 向けモックライブラリPersimmockを試作

モックフレームワークやモックライブラリは.NET界隈にいくつかあって、特にF#にはFoqがあるわけですが、現状で満足か、と問われると微妙な顔持ちになります。

以下、1年半くらい前にFoqの行けてないところをzeclさんに尋ねたときの会話です。

実際、Foqは(おそらく意図的に)緩めのAPIが定義されています。 例えばMock<IHoge>().Setup(fun mock -> <@ mock.Hoge(any(),any()) @>).Calls<string*int>(fun (_,x) -> x)だと、mock.Hoge(any())でもコンパイルに成功するはずです(テストは失敗する)。 .Calls<string*int>(fun (_,x) -> x)も引数の型をみているわけではなく、<string*int>というユーザの自己申告を信じています。 仕方がないといえばそこまでですが、もう少し型安全に挑戦してみても良いのではないか、という気持ちが少し芽生えます。

というわけでPersimmockというライブラリを試作しました。

解説?

https://www.nuget.org/packages/Persimmock/0.0.1

https://github.com/persimmon-projects/Persimmock

名称的にはPersimmonの系譜ですが、現状はPersimmon非依存です。 1年前にリポジトリだけ作って放置していたものを、謎の気力上昇によって2日くらいででっちあげた感じです。

このライブラリを1行で説明すると”ガワ変えのFoq”です。 どのくらいFoqかというと、動的生成部分やVerification部分のAPIはそのまま拝借した程度にはFoqです。

mock<IHoges> {
  method (fun mock -> mock.Hoge)
  call (fun (_, x) -> x)
}

こんな感じで書けます。 コンピュテーション式なのは、ReflectedDefinition(true)が使えるいい感じのAPIを思いつかなかったからです。

メソッドの引数の型を取るためにメソッドを直接とります。 FoqのMethodとほぼ同一です。

mock<IHoges> {
  setup (fun mock -> mock.Get(It.IsAny(), It.IsAny()))
  hook (fun (_, x: int) -> x)
}

いちおうFoqらしい書き方もできますが、基本推奨しません。 前者だとオーバーロードメソッドやsetterがうまく処理できないので緊急回避的に残した次第(どうやったらsetterを関数として取得できるんだ…?)。

hookは名前が思いつかずとりあえずで付けたものなので、たぶんそのうち変わります。

mock<IFuga> {
  property (fun x -> x.StringProperty)
  returns (fun () -> value)
}

プロパティは別APIで、returnsには即値を渡せません。 パフォーマンスと書きやすさとコンピュテーション式でオーバーロードできない問題を天秤にかけた結果です。

let xs = mock<IList<int>> {
  method (fun xs -> xs.Contains)
  args (any<int>)
  returns (fun () -> true)
}
xs.Contains(1) |> ignore
Mock.Verify(<@ xs.Contains(0) @>, never)
Mock.Verify(<@ xs.Contains(any<int>) @>, once)

argsで引数を渡します。 頑張って型安全みをだしてみましたが、かわりにジェネリックメソッドに対して無力になりやすいです…。

Foqのanyだと型推論が誤作動して2引数をタプルとして解釈し、実行時にエラーになりやすかったため、anyは型パラメータを明示する形にしました。 ただし、その分融通が利きません。 あえて型推論に身を任せたい場合はIt.IsAny()を使います。

VerifyはFoqそのままなので特になし。

未実装機能

Mock.~ByNameは型安全にできないのであえて実装していません。

Mock.Asはなんか過剰な気がしたのと、現状の実装と相性が悪すぎたので実装していません。

まとめ

型安全にしようとするとどこかが割を食う、ということがよくわかる試作結果でした。 もう少しどうにかできないものか…研究は続きそうです。

Foqのバックエンドよくできているので、ぶっちゃけそんなに手を入れる必要はないと思っています。

余談

ここまで作っておいてなんですが、個人的にはinterface + オブジェクト式でやっていこうなという気持ちになりました。