ビデオネタ: IL2C

IL2Cと言うのは、Unityで言うところのIL2CPPのC言語版です。これをフルスクラッチで作ってみるというビデオシリーズです。
つまり、C#(というか、それをコンパイルしたILを含むDLLやEXE)を入力に、C言語のソースコードを生成するツール、というわけです。

YouTube: Playlist: Making archive IL2C

現在第6回までやったところですが、以下のようなC#のコード:

public static int main()
{
  var a = 1;
  var b = 2;
  var c = a + b;
  return c;
}

が、

#include <stdint.h>
int32_t main(void)
{
  int32_t local0;
  int32_t local1;
  int32_t local2;
  int32_t local3;

  local0 = 1;
  local1 = 2;
  local2 = local1 + local0;
  local3 = local2;
  return local3;
}

というC言語のソースコードに変換できる所まで出来ました。また、これの64ビット(long, int64_t)も出来たところです。

開発の方法の検討・設計・実装・テストまで含めて、全体を一からフルスクラッチでやってるところを、ほぼカット無しで収録しています。メタプログラミングに興味のある方は視聴してみてください。

この調子で続けると、40回~50回ぐらいで完成かな… 先は長そうだ :)

あと、途中でHuman Resource Machineというゲームをちょっとだけ紹介しています。IL(というかアセンブリ言語)をゲーム感覚で学べるアプリでお薦めです。

ビデオネタ開始

こんにちは、最近ネタを楽に公開するいい方法が無いかと考えてたんですが、「そうだ、ビデオ公開すれば喋るだけだからいいんでね?」と、ちょっとおかしなことを考えたのでやってみました。掛け合い式にしたほうが面白んじゃないか(&初版出す勇気)ということで、@matsujirushi12さんと撮ってます。

YouTube: Center CLR channel
Channel9: Channel 9 author Kouji Matsui

下の埋め込みはYouTubeです。


About .NET Micro Framework in 2017

「2017年、.NET Micro Frameworkに動きはあったのか? NETMFの開発チームは2015年ごろから動きが鈍くなっていました。しかし、最近になって、NETMFのコードをフォークして移植性を高めようという動きがあります。このようなNETMFの新しい動きについて、ラフに総括してみます」


TinyCLR OS

「TinyCLR OSは、従来の環境よりも更に扱いやすくなっています。私たちはTinyCLR OSをどうやって始めれば良いか、デモも交えてディスカッションします」


Start F#

「これを見れば、F#の開発を始められる! 私たちはF#を使った開発の罠についてディスカッションしました。F#で楽しい開発をするポイントはどこにあるか?」


勉強会で初めて登壇するときに、聴衆に圧倒されたりするのって普通にあるんじゃないかと思ってるんですが(え?ない?)、「聴衆が居ない」カメラに向かって喋るというのも、一種の慣れが必要だなと思いました。まあ、喋りだせばそんなこと忘れてるんですがw それも相方のおかげですね。

ちょっとNGっぽいシーンもあったりしますがw そもそも「コンテンツを楽に公開する」のを趣旨としてるので、色々完璧にやる気は無いです(それだと意味がないので)。できるだけ「撮って出し」みたいな感じで行けるような方向で工夫をしていきます。楽屋ネタもそのうちやろうかと思うんですが、機材とか専門外なのでスムーズにやるのが難しい… そのうち知見も貯まるかな。

時間さえ取れれば、半定期的な感じで撮っていこうと考えてます。

それでは!

C#でわかる こわくないM – F# 勉強会 岐阜

今年久々の投稿。「C#でわかる こわくないM」というお題で、F# 勉強会 岐阜で登壇してきました。

「M」とは「モナド」のことです。内容について誰にも推敲依頼しなかったので、実際に発表するまで内容の正確性に自信がなかったのですが、大きな問題はなさそうでホッとしました。
最初に喋りましたが、これについては解説の筋道を2年ぐらい考えていました。なんとか形になってよかったのと、自分の口で説明することでより理解が深まったのが大きな収穫でした。

帰りにはもみあげさんとコレクションへのモナドの適用(要するにLINQのSelectMany)がモナドであるのか否かの話も出来たので、これも大きかったです。次はモノイドの理解とモナド・モノイドの(頭のなかでの)統合が課題かな。このネタは、Fsharp Bootcamp Tokyo 2016 with Tomas Petricekのときに出た、モナド(又はモノイド)に対するコンピュテーション式の適用での矛盾が元ネタで、この時にはまだ消化しきれなかったので、いつか噛み砕いて理解しようと思っていたことです。このあたりが理解できると、抽象化の道具がまた一つモノになったと言える気がします。今日の発表で、一歩近づいたかな。

* 内容はbleisさんのGistに書いてありますが、今見てもまだ怯んでしまうな…

それではまた。

.NET Core 2016年の締め – .NET Core Advent Calendar 2016

この記事は「.NET Core Advent Calendar 2016」の25日目の記事です(遅刻しました)。

今年は.NET CoreのRTMがあったので、知名度も上がったのではないかと思います。その辺りを適当に回想したいと思います(深い話はなし)。


.NET Coreという存在

.NET Coreは次世代の.NET Framework、のようなざっくりとした紹介をされる事が多いです。製品的にはそのような位置づけであり、実際初期のプロジェクトはそういう方向性であったのではないかと思いますが、現在はちょっと印象が異なります。

netcore1.NET Coreが議論に上がる頃にMSが度々提示していた図がコレです。図自体もシンプルで、.NET Framework 4.6と.NET Core 5が両立され、従来の.NET Framework 4.6は互換性のために維持され、.NET Core 5はマルチプラットフォーム対応となることが示されています。

netcore2次のこれは、.NET Core 5がリバージョニングされて.NET Core 1.0となり、正式リリースされた頃の図です。見たところ、.NET Frameworkと.NET Coreの位置づけは変わっていません。そして、このころはまだ.NET Coreの役割としては「ASP.NET Core」に向けられていたことが分かります。

これを見ると、この時点ではまだ「Xamarin」が壇上にないですね。実際、まだMSはXamarinを買収していません。

netcore3この辺から変化が始まります。まず、.NET CoreとUWPの関係が示されるようになりました。それどころか「Any other app model」のような何かが示されていますね。そして、それらを統括するかのような「Unified BCL (Base Class Library)」という中間レイヤーのようなものが鎮座するようになりました。

# この図では、.NET Frameworkは省かれています。

netcore41一時、もっとも状況を良く表した図がコレだと思います。C#とVB.NETが上辺に、それらを使って従来通りのWebFormやMVC、WebAPIが.NET Framework上でサポートされる一方、新しいASP.NET CoreはCore CLRと.NET Native(あまり語られていないように思うのですが、.NET Coreはコンパイル時にネイティブバイナリを生成できます)が.NET Coreの上で動くこと。そして、.NET Framework上でもASP.NET Coreが動くことが示されました。

この図から、各コンポーネントの関係性が段々と複雑化してきていることがうかがえます。

netcore5最近語られるのがこの図です。Xamarinが正式に.NETのエコサイクルの一部として認知され、.NET Coreは「ランタイム」と「インターフェイス仕様」に二分されました。.NET FoundationというOSS団体が、「.NETの基本となるライブラリの外部規約」(例えば、どんなクラスがありどんなメンバが定義されているべきか)を査定し、これに「.NET Standard」と名前を付けました。

.NET Framework、.NET Core、Xamarin (Mono) など、各個別の.NET実装はこの外部規約に沿って再定義されたバージョンを、リリースする(あるいはリリースする予定)となりました。


.NET Standard(仕切り直し)

開発者は.NET Standardの規約を想定して開発を行うことで、これらのプラットフォームで共通に使用可能なライブラリのセットを想定できるようになりました。但し、これにはバージョンがあり、1.0~1.6、そして現在2.0を正式査定する準備を行っています。

netcore6この図は、.NET Standardのバージョンと、各プラットフォーム別のバージョンの対応を示した図です。わかりにくいのですが、例えば.NET Core 1.0の環境を使う場合、.NET Standard 1.0~1.6の規約を想定できる、と言うように読みます。

最も歴史の古い.NET Frameworkについてみてみると、.NET Framework 4.6の環境を使う場合は.NET Standard 1.0~1.3までの規約が想定できるわけです。また、.NET Standard 2.0を見ると、.NET Framework 4.6.1を想定するように「後方互換性」を取り戻していることも読み取れます。.NET Frameworkの更新が追い付かない(.NET Frameworkのランタイムの更新が早すぎることを良しとしない顧客の都合のような)事が見て取れます。

# 注: 私の勝手な想像です。

「Windows」というのは、紛らわしいのですが、Windows 8.1までの「ストアアプリ」に相当するプラットフォームの事です。ごく自然な流れでUWPに移行してしまったのと、Windows 8系列が失敗とみなされている(たぶん)事から、影が薄い存在です。ストアアプリでは、.NET Standard 1.0~1.2までを想定出来ますが、1.3以降の規約は「使用できない」ことも読み取れます。

Xamarin環境は、全部.NET Standard 2.0以降の規約でしか使用できないように読み取れます。実際には従来通り、PCL (Portable Class Library) を使えば、ほかのプラットフォームとの共通利用可能なライブラリを使う/作ることが出来ます。将来的には対応してくはずです。

.NET Standardは従来のPCLの規約を置き換えるものですが、主な動機付けとしては、PCLは各プラットフォーム毎に上位互換とか下位互換とかの概念が希薄で、それぞれのプラットフォームの都合に合わせて共通仕様を決めていた都合で、プラットフォームの増加に合わせて非常に複雑化してしまった、という事があります。その複雑性は組み合わせで増加するため、何か新しいプラットフォームに対応させようとすると、もう人間には正確に判断出来ない所まで来てしまっていました。.NET Standardに規約を統合していくのは、仕方のない面もあります。

そのようなわけで、私は.NET Standardを、.NETのライブラリ規約に置いての「仕切り直し」と位置付けています。

OSSに引っ張られ始めたマイクロソフト

dotnet_logo先日、某氏と話をしていて改めて再認識したのですが、MSがOSSに舵を切り始めた時から、MSの製品プロジェクトは遅かれ早かれOSSプロジェクトに引っ張られざるを得ない、という仮説です。と言うのも、この記事を書いていても思ったのですが、上のバージョンの表からも、.NET Framework(厳密に言うなら: プロプラエタリなWindows上の.NETランタイム実装)のリリースタイミングを、.NET Standardの歩調に合わせられていない、という事実です(もちろん、様々な要因があるのだとは思いますが)。

Windows環境は、環境を固定的に扱ったり想定したりする用途が多いように思います。何か問題が発生することを極度に恐れ、変化を肯定的に捉えられない環境で使われるために、ちょっとした問題が大きく取り上げられたりします(主観なので、もちろんそうではない環境もあると思います)。そういう環境での変化が許されないという文化が、.NET Frameworkの想定バージョンをあえて戻している(4.6.1)ようにも思えます。

しかし、そんなことはお構いなしに、OSSプロジェクトは進行します。また、多くの人がかかわっているので、もやはこの流れを止めることは出来ないでしょう。また、もたもたしていると、競合に技術で抜かされてしまいます。そうなっては、何のためのプロジェクトかわかりません。

# しかも競合はそういう足かせが無かったりするので、あまり拘っているとそもそも土俵にすら登れない。

結果として、プロプラエタリ製品はOSSプロジェクトの都合に引っぱられるようになるのではないかと予想していました。これが解消されるためには、プロプラエタリ製品もその進行の速さにしがみ付いていくしかないんじゃないかなと思っています。それはWindows Updateのようなサブコンポーネントにも表れるだろうし、その土台の上で開発をする私たちにも影響すると思います。

MSが批判を受けて、何らかの形で.NET Foundationを操ろうとしてももう無理なところまで来ているし、以前にも増して、.NETとMSの行動を「厳しい目で見ている人たち」が増えているはずです。私自身は、今後MSがどのようにこの問題に対処していくのかを、楽しみに見ています。


project.json(安楽死)

netcore7project.jsonは.NET Core 1.0で導入された、新しいプロジェクトの管理手法で使うファイルです。JSON形式で、従来*.csprojや*.fsprojが担っていた「プロジェクトの構成情報」を格納しておきます。何故*.csprojをやめたのかと言うのは良く知らないのですが:

  • XMLでの管理がイケてない – 最近のjs界隈ではJSONで管理するのが流行っているので、それに合わせたい(?) ひょっとするとVSCodeのようなエディタ回りを拡張する開発環境での発展を見込んだという事もあるかもしれません。
  • .NET Coreはマルチプラットフォームなので、MSBuildが使えない – MSBuildはWindowsに依存する部分が多いため、移植を端から諦めて、全く新しい方法に乗り換えることを模索した(?)
  • NuGetによるライブラリの管理が複雑化しそうなので、これを統合したい。

で、.NET Coreのビルド制御には「dotnetコマンド」を使うので、こいつが直接JSONを解析すればいいのでは? と言う発想から生まれたような感じがしています(主観)。しかし、皆さんご存知のようにRTM直前になって急にproject.json方式は放棄すると言い出した… あまりに反対が多かったからと言う事のようです。

ですが、結局対策はRTMに間に合わず、.NET Core 1.0ではproject.jsonを使うという仕様で見切り発車。で、どうなったかと言うと、MSBuildをオープンソース化してこれをマルチプラットフォーム対応させるように移植して、*.csprojを復活させるという、初めからやれば順当な手段を採用することになりました。

# ここでもプロプラエタリ技術がOSSに振り回されている、という見方も出来る…
# (否定的文だけど、私的にはむしろ歓迎)

MSBuildの移植は結構問題もあったようですが、現在は.NET Standard 2.0の規約に合わせて(つまりVS2017のリリース?)使えるようにしようとしているようです。すると、.NET Core 1.0で使えたproject.jsonは、さっそく.NET Standard 2.0で非推奨となると。まあ、死ぬなら早い方が良いですね、どうせまだ本気で.NET Core使っているところは少ないだろうし(こなみ

それに、MSBuildが復活することで、これまでMSBuildスクリプトで使えたテクニックが再び使用可能になるのは大きいです。おまけにNuGetのどうしようもないアレな仕様も、とうとう完全にMSBuildに統合され、さんざん煮え湯を飲まされてきた仕様が一気に解決に向かいそうです。例えば:

  • NuGetパッケージを導入すると*.csprojを書き換えたりしますが、これが失敗して*.csprojが壊れたりおかしなことになったり。
  • NuGetパッケージの位置を示すパスがバージョンの更新でなぜか正しいパスを示さなくなってビルドに失敗するとか。
  • 古いパッケージの定義がゴミとして残り続けたりとか。
  • 古いNuGetを想定しているパッケージが、PowerShellで無理やり構成書き換えてて壊れるとか。

振り上げた拳の着地点に毎回困ったりしたのですが、NuGet 4.0と新しいMSBuildでかなり解消しそうです。MSBuild自体どうなのかという感もあるにはあるのですが、カオスな状態を一度はきちんとした着地点に落として、それから次に進んでもいい内容だと思います。


総評

.NET Core 1.0は決して順調な仕上がりではありませんでした。ただ、楽観視はしています。何故なら、OSSプロジェクトで進行することにより、開発プロセスが多くの人の目にとまり、明らかにおかしな方向に行くことに対してフィードバック(ツッコミ)が早く入れられるようになった事が大きいです。project.jsonのような過ちへの対処も、いつまでも汚い手法で姑息に対処し続けるのではなく、可能な方法で最善な手法は何か、と言う事に、多くの頭脳が取り組んでいると思います。

従来のWindows周りの開発では「変わらない」と言う事を主軸に、一種の安心感を持ってプラットフォームを採用してきたと思います。これが未来永劫うまくいくという確信がある話なら、全く問題ない、むしろ安定していて欲しいと願うばかりですが、残念ながら現実はそうじゃない。であれば、変化に追従できるような柔軟さを、.NET自身も、技術者の我々も、持ち続けられるようにしたいと思います。

来年は.NET Standard 2.0のリリースとともに.NET Coreの新しいバージョンもリリースされます。バラバラになっている様々な要素が次第に近寄ってくることになり、ようやくシナジーが生まれ始める年になるんじゃないかなと楽しみです。

.NETの新たな幕開けに期待して。

About Expandable F# Compiler project – F# Advent Calendar 2016 (Japan)

christmas_zangyou_manこの記事は、「F# Advent Calendar 2016」の23日目の記事です。やっばいギリギリ。

英語版はこちら (For English post: https://www.kekyo.net/2016/12/23/6305)

前日は、ぜくるさんのTypeProviderのお話です。

今私は、「F#のコンパイラの代替え品」を作っています。とは言っても、一からF#のコンパイラを自作できるような技量はあるはずがなく、「F# Compiler Service」をバックエンドとして使っています。

F# Compiler Serviceは、C#でいう所のRoslynに相当して、F#ソースコードをパースして構文木(Abstract Syntax Tree)を取得し、さらにそれを使って実際にコンパイル(つまりMSILを生成してPEを出力する)ところまでをプログラマブルに実行できるライブラリです(他にも、fsiに相当するInteractive interpreterのエンジンも持っています)。

何故、代替えとなるコンパイラを作ろうとしているのかとか、背景についての紹介をしたいと思います。


Expandable F# Compiler project (fscx)

fscx_512Expandable F# Compiler (fscx)は、GitHubで公開しています。ちなみに現在のバージョンは0.6.14、まだ正式リリースには至っていません。仕様は予告なく変更される可能性があります :)

fscxは、標準のF#コンパイラ(fsc.exe)の代わりに使用します。目的とかポイントは以下の通りです:

  • コンパイル時に「ソースコードの自動変形」を可能にしたい – これは、ソースコードのテキストベースの置換(例えば正規表現などを使って)するのではなく、F# Compiler Serviceで構文木を取得した後、その構文木に直接手を入れて変形させることを目指しています。テキストベースの置換では、明らかに不正な変形を行ってしまう危険性がありますが、構文木ベースであればそのような問題は発生しません(実際には非常に難しいのですが)。
  • この自動変形を行う処理を「フィルタライブラリ」と呼び、フィルタライブラリは自由に拡張出来るようにする – 例えば、当初想定しているのは、目的となるメソッドの呼び出しコードの前後に、自動的に追加コードを(構文木ベースで)挿入するフィルタライブラリです。しかし、これに限らず、様々な変形をプラガブルに実現できるようにします。
  • 導入が簡単である事 – コンパイラパスの手動構成やVSIXではなく、「NuGetパッケージ」として公開し、NuGetインストールするだけでfscxを使用するようにできる事。これは、以前「NuGetでビルドプロセスに介入するパッケージを作る」で書いた通り、MSBuildのスクリプトを書くことで実現できました。

使用者から見た構造

slide1fscxの使用者は、fscx自体を直接扱うわけではありません。目的別に作られた「フィルタライブラリ」がNuGetで導入できるようになるため、これを導入することで、依存関係によりfscxが導入されて、標準のfsc.exeを置き換えます。

この図では、「sample-filter.nupkg」というNuGetのパッケージがあり、これを自分のF#プロジェクトに導入すると、fscx(実際には”FSharp.Expandable.Compiler.Build”)も一緒に導入され、fsc.exeではなく、fscxを使ってコンパイルを行うようになります。そして、sample-filter.nupkgに含まれるフィルタライブラリを使って構文木を変形し、バイナリを生成します。

例えば、以下のようなコードを書いていたとします:

module SampleModule =
  let format (a: int) (b: int) (c: string) =
    System.String.Format("{0} = {1}", a + b, c)

もし、sample-filterが「関数(メソッド)の呼び出し時にログ出力を挿入する」フィルタライブラリだったとすると:

module SampleModule =
  let format (a: int) (b: int) (c: string) =
    // 以下のコードが自動的に挿入される
    System.Diagnostics.Trace.WriteLine("Log: format {0} {1} {2}", a, b, c)
    System.String.Format("{0} = {1}", a + b, c)

のような変形を「コンパイル時」に実現します。もちろん、ソースコードファイル自体に一切変更は加えません。そして、使用者はsample-filter.nupkgを導入するだけでこれが実現できるようになります。

使用者がフィルタの詳細に関与することなく、フィルタパッケージの導入だけで実現する – このようなツールセットが透過的に振る舞う必要があると言う点で、非常に重要だと考えています。

フィルタライブラリ開発者から見た構造

slide2フィルタライブラリ側は、やや複雑です。

まず、フィルタ処理はF# Compiler Serviceの構文木を使うため、必ずF# Compiler Serviceへの参照が必要になります。また、fscxの中核となるライブラリも参照が必要です。fscxは”FSharp.Expandable.Compiler.Core”で公開されているので、自分のフィルタライブラリプロジェクトに導入しておきます。

そして、実際にフィルタコードを書けばよいのです…が…


フィルタコードの実装手段

フィルタライブラリを作る際に、2通りの方法を選択することが出来ます:

  • 構文木に対して、レガシーなビジターパターンを使う – これは、.NET LINQで使われている「ExpressionVisitorクラス」と同じようなクラスを使って、いわゆるOOPベースのビジターパターンで構文木の変形を行う方法です。なぜExpressionVisitorクラスをそのまま使わないのか?と思うかもしれません。これはF# Compiler ServiceやRoslynを使ったことがある方ならわかると思いますが、構文木の構造は言語毎に大きく異なるのです。同じRoslynでも、実はC#とVisual Basicが似て異なる構文木クラス群を使うように、F#も全く異なる構文木型を使います。つまり、既存のVisitorクラスはどれも流用出来ないため、専用の基底Visitorクラスを用意しています。
  • F# Compiler Serviceは、ビジターパターンを適用できる標準的なインフラを用意していません – 何故なら、関数プログラミングとパターンマッチングを使うと、わざわざ基底Visitorクラスのようなものを用意しなくても、簡単にビジターパターンを適用できるからです。とは言っても、再帰的な探索のうち、構文木のリストなどは式で書くと煩雑になりがちです。そこで、関数プログラミングスタイルで使用できる、再帰探索の実装を簡略化可能な補助ライブラリを用意しました。

例えば、基底Visitorクラスを使うと、以下のようにコードを書くことが出来ます:

type InsertLoggingVisitor() =
  inherit AstInheritableVisitor()

  override __.VisitExpr_Quote(context, operator, isRaw, quoteSynExpr, isFromQueryExpression, range) =
    // DEBUG
    printfn "%A" operator
    base.VisitExpr_Quote(context, operator, isRaw, quoteSynExpr, isFromQueryExpression, range)

  override this.VisitExpr_App(context, exprAtomicFlag, isInfix, funcExpr, argExpr, range) =
    match funcExpr with
      // ...

関数ビジターパターンのための補助ライブラリを使った場合:

let outerVisitor
   (defaultVisitor: (NoContext * SynExpr -> SynExpr),
    context: NoContext,  // (Non custom context type)
    expr: SynExpr) : SynExpr option =

  match expr with
  | SynExpr.Quote(operator, _, _, _, _) ->
    // DEBUG
    printfn "%A" operator
    None  // (None is default visiting)

  | SynExpr.App(exprAtomicFlag, isInfix, funcExpr, argExpr, range) ->
    match funcExpr with
      // ...

  | _ ->
    None  // (None is default visiting)

どちらが望ましいかは、一長一短あります。基底Visitorクラスを使う場合は、すべての構文木ノードをビジターの対象に出来ますが、ネストした構文木の解析は煩雑です。関数ビジターパターンは解析の自由度が高く、パターンマッチングを使用して容易に構文木を特定できますが、ビジターの対象はSynExprのみです。

これらの補助ライブラリは、fscx自身に直接依存しないため、「FSharp.Compiler.Service.Visitors」という独立したパッケージにしてあります。そのため、fscxを使わないとしても、このライブラリだけで、F# Compiler Serviceのビジターパターンの実装に流用することもできます。

# このライブラリは0.7.1で大体固まった感があるので、正式リリースまで大きく変わらないと思います。


構文木変形の難しさ

構文木の変形には、一種のロマンがあります(たぶん ;)

しかし、実際には「死ぬほど大変」です。この記事を読んでいる人は、おそらくF# Compiler ServiceやRoslynを日常的に触っていると思う :) ので、それがいかに大変かおわかりいただけると思います。LINQのExpressionTreeも大概ですが、あれが更に複雑になったものと思ってもらえれば良いと思います。

# なお、ExpressionTreeについては、Advent LINQの後半の記事や、Final LINQ Extensions IIIが参考になると思います。

前述のsample-filterで示したような変形を実現するには、狙った場所に対応する構文木がどのような構造で現れ、それをどのように変形すれば目的を達成できるのかを、慎重に検討しなければなりません。

構文木は再帰構造で定義されます。そのため、いい加減な判定を行うと、意図しない箇所を変形してしまう可能性があります。また、ソースコード上では些細な違いでも、構文木上ではドラスティックに変わってしまったりすることもあります。

例えば、以下のようなコードを考えます:

// 一般的な.NETのメソッド(タプル形式)
let output1 (a: int, b: string, c: double) =
  System.Console.WriteLine("output1: {0}:{1}:{2}", a, b, c)

// F#関数(カリー化可能形式)
let output2 (a: int) (b: string) (c: double) =
  System.Console.WriteLine("output2: {0}:{1}:{2}", a, b, c)

output1は、典型的な.NET標準のメソッドと呼べるでしょう。しかし、output2はF#らしい「関数」です。このoutput2を呼び出すコードを書いたとします:

// 一般的な.NETのメソッド呼び出し(タプル形式)
// (int * string * double) -> unit
output1 (123, "ABC", 456.789)

// F#関数の呼び出し(カリー化可能形式)
// int -> string -> double -> unit
output2 123 "ABC" 456.789

.NETメソッド形式は、以下のF#関数と区別して「タプル形式」と呼んでいます。構文木上でも、引数部分が「タプル」のように見え、一つのタプル値を引数に渡しているかのように解釈されます。F#関数は「カリー化可能形式」です。カリー化可能の場合の関数呼び出しは、部分適用された関数が連続的に適用されていくような感じに解釈されます。そして、構文木上もネストされた関数呼び出し(Appノード)で表現されます。

構文木が可視化できないと、ここら辺の検討が絶望的に大変なので、小さなツールですが構文木をF#コードっぽくダンプするツールを作りました(可視化が目的だったので、完成度は高くありません)。これを使うと、以下のような出力が得られます:

// 以下がoutput1の呼び出し
Ast.SynExpr.App( (* expr1 *)          // App - output1(...)
  ExprAtomicFlag.NonAtomic (* exprAtomicFlag *),
  false (* isInfix *),
  Ast.SynExpr.Ident( (* funcExpr *)
    output1 (* Item *)),
  Ast.SynExpr.Paren( (* argExpr *)    // Paren --> Tuple : タプル形式の引数
    Ast.SynExpr.Tuple( (* expr *)
      [ (* exprs *)                   // タプルの中身をリストで表現
        Ast.SynExpr.Const( (* [0] *)
          Ast.SynConst.Int32( (* constant *)
            123 (* Item *)));
        Ast.SynExpr.Const( (* [1] *)
          Ast.SynConst.String( (* constant *)
            "ABC" (* text *)));
        Ast.SynExpr.Const( (* [2] *)
          Ast.SynConst.Double( (* constant *)
            456.789 (* Item *)))],
      [ (* commaRanges *)]),
    range>.Some( (* rightParenRange *)))),

// 以下がoutput2の呼び出し
Ast.SynExpr.App( (* expr2 *)          // App - 456.789の適用
  ExprAtomicFlag.NonAtomic (* exprAtomicFlag *),
  false (* isInfix *),
  Ast.SynExpr.App( (* funcExpr *)     // App - "ABC"の適用
    ExprAtomicFlag.NonAtomic (* exprAtomicFlag *),
    false (* isInfix *),
    Ast.SynExpr.App( (* funcExpr *)   // App - output2に対して123の適用
      ExprAtomicFlag.NonAtomic (* exprAtomicFlag *),
      false (* isInfix *),
      Ast.SynExpr.Ident( (* funcExpr *)
        output2 (* Item *)),
      Ast.SynExpr.Const( (* argExpr *)
        Ast.SynConst.Int32( (* constant *)
          123 (* Item *)))),
    Ast.SynExpr.Const( (* argExpr *)
      Ast.SynConst.String( (* constant *)
        "ABC" (* text *)))),
  Ast.SynExpr.Const( (* argExpr *)
    Ast.SynConst.Double( (* constant *)
      456.789 (* Item *))))),

出力が冗長で見難いのですが、output1とoutput2では、引数がリストで表現されているか、ネストされた関数として表現されているかで全く構造が異なることが分かります。カリー化可能形式とは、実は以下のような呼び出しであることが分かります:

// F#関数の呼び出し(カリー化可能形式)
// int -> string -> double -> unit
output2 123 "ABC" 456.789

// 実はこういう事:
((output2 123) "ABC") 456.789

// さらに分解すると:
let f1 = output2 123  // f1は高階関数
let f2 = f1 "ABC"     // f2は高階関数
f2 456.789

ソースコードの些細な違いが、構文木上で大幅に異なる表現となる場合があるため、ビジターパターンで探索する場合に注意が必要です。実際のところ非常に難しい…

# 余談ですが、構文木の解析が非常に大変だった経験から、先日のNGK2016BのLTネタ: 「You will be assimilated. Resistance is futile.」が生み出された、みたいな背景があります。Roslynなので、fscxとは全然関係ないのですが :)


フィルタのミドルウェア

このように、フィルタライブラリの独自実装自体は、構文木の扱いが非常に複雑であるため、特に想定されるシナリオのために、ミドルウェアとなる補助ライブラリ”FSharp.Expandable.Compiler.Aspect”を用意しています。

これは、「AOP(アスペクト志向パラダイム: Aspect-Oriented-Paradigm)」で知られているような、任意の処理の直前と直後に、安全に処理を挿入できる機構を持った手法と同じことを、fscx上で実現させるための補助ライブラリです。

フィルタライブラリを開発する際に、一からフィルタライブラリを実装するのではなく、この補助ライブラリを使うと、fsprojのプロパティに指定した情報と「アスペクトクラス」を指定して、構文木の操作を一切実装することなく、安全にAOPを実現できます。

例えば、以下のようなコードを用意しておきます:

// コンテキストクラス(関数を抜ける際に実行するメソッドを定義)
type SampleAspectContext internal (body: string) =
  // Finish aspect (trigger are leaved method with return value)
  member __.Leave(result: 'T) : 'T =
    Console.WriteLine("Leave: " + body)
    result
  // Finish aspect (trigger are leaved method with exception)
  member __.Caught(ex: exn) : unit =
    Console.WriteLine("Caught: " + body + ": " + ex.ToString())

// 関数を呼び出す際に呼び出されるメソッドを定義
type SampleAspect() =
  // Start aspect (trigger are entered method)
  static member Enter(methodName: string, fileName: string, line: int, column: int, args: obj[]) =
    let body =
      String.Format
        ("{0}({1}, {2}): {3}({4})",
         fileName,
         line,
         column,
         methodName,
         String.Join(", ", args |> Seq.map (sprintf "%A")))
    Console.WriteLine("Enter: " + body)
    new SampleAspectContext(body)

このアスペクトコードは、関数の入り口と出口で、自動的にコンソールにログを出力します。SampleAspect.Enterが関数の入り口で呼び出され、関数名やソースコードの位置、そして渡された引数を入手できます。この情報をもとに、ログを出力しています。

また、Enterが返すクラスのインスタンスが「アスペクトコンテキスト」として扱われ、LeaveまたはCaughtが関数の出口で呼び出されます。命名の通り、正常に抜ける場合にはLeaveが、例外で抜ける場合にはCaughtが呼び出されます。それぞれ、戻り値と例外のインスタンスが引数で指定されるので、これを元にログを出力しています。

これらのクラスや関数とその引数は、完全なダックタイピングです。特定のクラスやインターフェイスの実装は不要で認識されます。本当はインターフェイスで縛ったりしたいのですが、そうすると余計なアセンブリ参照が増え、最終的にはNuGetのパッケージングで非常に苦労することになるからです。

これで、アスペクトを「SampleAspect」として定義できたので、FSharp.Expandable.Compiler.Aspectに認識させるために、以下のコードを定義しておきます:

// SampleAspectクラスをアスペクトとして使用するフィルタを定義する
[<Sealed>]
type SampleAspectLoggerDeclaration() =
  // 依存関係を増やさないために、クラス名を文字列で指定
  inherit DeclareFscxInjectAspectVisitor("SampleAspectLogger.SampleAspect")

アスペクトコード自体とこの定義型の実装は、C#でも記述できるように、型の要求を緩くしてあります。

この定義がフィルタライブラリに含まれていると、fscxによって以下の操作が行われます:

  • 対象の関数の入り口に、SampleAspect.Enterを呼び出すコードを、構文木の挿入で実現する。
  • 対象の関数の出口に、SampleAspectContext.LeaveまたはCaughtを呼び出すコードを、構文木の挿入で実現する。Leaveは正常終了した際の戻り値を、Caughtはtry-withブロックによってキャッチした例外を伴って呼び出す。

このようなコードが:

let f11 (a: int, b: string, c: int) =
  output1(a + c, b, 123.456)

fscxによってこのように変形されます:

let f11 (a: int, b: string, c: int) =
  let __arg_0 = a + c
  let __arg_1 = b
  let __arg_2 = 123.456
  let __context =
    SampleAspectLogger.SampleAspect.Enter
      ("f11", "SampleCode.fs", 2, 3, [|__arg_0, __arg_1, __arg_2|])
  try
    __context.Leave(output1(__arg_0, __arg_1, __arg_2))
  with
  | ex ->
    __context.Caught(ex)
    reraise()

ここでは、タプル形式のメソッドを呼び出していますが、当然カリー化可能形式にも対応しています。

アスペクトクラス(SampleAspect, SampleAspectContext)と、アスペクトクラスを定義する型(SampleAspectLoggerDeclaration)以外に、構文木を操作するためのコードは一切不要であることに注目してください。

なお、現在FSharp.Expandable.Compiler.Aspectは実装を行っている最中で、特にどの関数を変形のターゲットとするかという指定方法を詰めている所です。


まとめ

このように、fscxはこれを土台として、FSharp.Expandable.Compiler.Aspectを使えばAOPを簡単に実現させることも出来、それ以上の変形も(難易度は高いですが)自在に可能でかつ、これをフィルタライブラリとして配布・再利用可能にします。

例えば、F#でOOPをやるのは非常にダルい説があります。何故なら関数プログラミングスタイルになれると、クラスとかインターフェイスを定義したり使ったりするのが面倒なためです(人によるかも知れませんが)。

そのようなわけで、F#でUIプログラミングをやるために、INotifyPropertyChangedのハンドリングとかも面倒この上ないのですが、fscxがあれば、インターフェイスの自動実装を行う変形フィルタライブラリを作ったりとかも出来るのです(しかも、コンパイル時に変形するのでランタイムコストがありません)。そういうライブラリがあれば、普通にコードを書いて、フィルタライブラリのパッケージをNuGetで導入するだけで、即自動実装させることが出来ます。

正式リリースまでには、まだいくつか詰める必要があるのですが、着地点は見えてきた感じです。

謝辞: 株式会社オンザロードさんと、ぶれいすさんに、このプロジェクトの協力を頂いています。ありがとうございます。

今年も残すところあとわずかですね。それではまた!

About Expandable F# Compiler project – F# Advent Calendar in English 2016

This post is 23th “F# Advent Calendar in English 2016.”

For Japanese post: (https://www.kekyo.net/2016/12/23/6263)

Hi, all F#er!! My name is Kouji Matsui, I live in Japan and I’m architect/developer.

I’m interesting to C#, LINQ, F#, IL and metaprogramming. I know about what/how asynchronosly execution between user mode .NET handler and kernel-mode driver DPC/hardware interruption. Titled Microsoft MVP for VS and DevTech. Holds CSM, CSPO. And bicycle hobby rider. I’m already released “F# FusionTasks.”

If you want contact to me, please use twitter: @kekyo2 or gitter: kekyo/public

# Sorry I have poor English, this post is very challenging for me :)

Currently, I’m developing an alternate compiler for F#. This is not meaning I’m compiler specialist :) This project uses the “F# Compiler Service.”

F# Compiler Service is likely “Roslyn” under C#. That’s parsing for F# source code and output AST (Abstract syntax tree). Then can compile and output to executable binary from ASTs. Another usage, F# Compiler Service have capability of interactive interpreter for F# (backend for fsi).

I try to write this project’s background: Why do I develop alternate compiler for F#?


Expandable F# Compiler project (fscx)

fscx_512“Expandable F# Compiler (fscx)” is an open source project on GitHub. Current version is 0.6.14, not reached official release. This post’s details will be implicitly changing :)

We can replace standard F# compiler (fsc.exe) to fscx. fscx’s goals are:

  • I want to auto translate the F# source codes – We can translate source codes by text-based (For example use by regular expressions). But this project not uses text-based translate. I’m designing for use F# Compiler Service and get ASTs, then translate ASTs directly. Because try with text-based translates then broken codes easier. AST based approach will not break and safe (But it’s very difficult for me, see below section :)
  • This auto translate library called “Filter library” and I want to can be extendable and expandable for non limitation – For example, a filter library can handles for insert another code fragments to before/after method body (by uses AST translate. likely AOP) Sure this is example, we can handle ANYTHING by AST translations.
  • Better easier installation – NOT customize for compiler executable path, NOT require install VSIX plugin. fscx is packaging by NuGet, we can use fscx on target F# project, require only fscx install via NuGet, simply then enables fscx. This senario already examine how to effect for building system by MSBuild (Post: “How to intercept for building system by NuGet,” In Japanese)

Structure viewed from the filter library user

slide1For fscx users, fscx uses not directly. We are implicitly using for fscx depended from the “Filter library” via NuGet. We can select filter library from the purpose. If installed fscx then auto replace the F# compiler (fsc.exe) to fscx.

For example, this slide identicate the box “sample-filter.nupkg.” This is packaged filter library on NuGet. We download and install this package via NuGet. NuGet client auto install depended packages include fscx named “FSharp.Expandable.Compiler.Build.” And ready for auto translate ASTs by filter library and fscx. Then compile and output translated binary.

Example in this code fragment:

module SampleModule =
  let format (a: int) (b: int) (c: string) =
    System.String.Format("{0} = {1}", a + b, c)

If “sample-filter.nupkg” contains AST translator implementation for: “Insert logging codes on function entry point:”

module SampleModule =
  let format (a: int) (b: int) (c: string) =
    // Auto inserted logging code below:
    System.Diagnostics.Trace.WriteLine("Log: format {0} {1} {2}", a, b, c)
    System.String.Format("{0} = {1}", a + b, c)

This translation executes at compile time. Sure no text-based translation. Then we can effect this translation for only installing filter library via NuGet.

Very important at this point: Users don’t know about filter implementation details. I’m designing for fscx to do in transparents.

Structure viewed from the filter library developer

slide2More complex for filter library side.

Filter library must use F# Compiler Service’s AST types, so library has assembly reference to F# Compiler Service and the fscx core library too. fscx core library package is naming for “FSharp.Expandable.Compiler.Core.” This package install to filter library project via NuGet.

Ready for can develop filter implemens, but…


How to implemens filter library

You can choice “Visitor patterns” on the helper library:

  • The legacy OOP based visitor pattern – This is most known technics for recursive tree structure on OOP. This approach likely .NET LINQ’s “ExpressionVisitor class,” named “AstInheritableVisitor class.” Why do we use AstInheritableVisitor instead ExpressionVisitor? Because F#, C# or any other languages (include .NET Expression-tree) different expression nodes smaller and larger. (You are already known things if you are using for F# Compiler Service or/and Roslyn :)
  • F# Compiler Service not provides standard visitor pattern infrastructures – Because we can implement visitor pattern with both the functional programming (FP) technics and pattern matching. But code fragments must has frequently recursive calls. So I developed visitor pattern helper functions on FP-based contains recursive callers.

For example, How to do using for OOP based visitor class:

type InsertLoggingVisitor() =
  inherit AstInheritableVisitor()

  override __.VisitExpr_Quote(context, operator, isRaw, quoteSynExpr, isFromQueryExpression, range) =
    // DEBUG
    printfn "%A" operator
    base.VisitExpr_Quote(context, operator, isRaw, quoteSynExpr, isFromQueryExpression, range)

  override this.VisitExpr_App(context, exprAtomicFlag, isInfix, funcExpr, argExpr, range) =
    match funcExpr with
      // ...

How to do using for FP based helper functions:

let outerVisitor
   (defaultVisitor: (NoContext * SynExpr -> SynExpr),
    context: NoContext,  // (Non custom context type)
    expr: SynExpr) : SynExpr option =

  match expr with
  | SynExpr.Quote(operator, _, _, _, _) ->
    // DEBUG
    printfn "%A" operator
    None  // (None is default visiting)

  | SynExpr.App(exprAtomicFlag, isInfix, funcExpr, argExpr, range) ->
    match funcExpr with
      // ...

  | _ ->
    None  // (None is default visiting)

Which choice of better technics? There is both merits and demerits which is desirable. If you use OOP based, you can receive all AST nodes at visit timing but more complex and difficult for how to identicates structurable-nested AST nodes. If you use FP based, can easier structurable-nested AST nodes (by pattern matching) but you can receive only “SynExpr” nodes at visit timing.

These helpers are totally not depended on fscx. I separate visiter pattern helpers from fscx and construct standalone package for “FSharp.Compiler.Service.Visitors.” If you are thinking about how to apply visitor pattern for your application, try to use this package.

# This package fixed for the most part in version 0.7.1.
# Maybe not change for public declarations.


What difficults for AST translations?

The AST translation code has cool things (Maybe ;)

But AST translation always most difficult technics in real story. You are the man of always designing for F# Compiler Service/Roslyn’s AST everydays :) So you are understand it’s really difficult for meaning and knowning. F# ASTs are likely Expression-tree classes, but translation is difficult more than ExpressionVisitor experience…

# If you want more informations about Expression-tree, read these posts: Second half of Advent LINQ 2013 (In Japanese) and/or “Final LINQ Extensions III” (In Japanese).

We are trying to implement the “sample-filter” above: We need knowns about what realize AST structures and where AST nodes appearance for? And how to translate AST nodes?

AST nodes constructs for recursivility. So if you identicate AST nodes by rough technics, results are broken easier. (OK, F# Compiler Service find invalid AST structures and report errors at compile time. The world maybe safe :)

This example illustlated for how different AST structures on source code by small different:

// The standard .NET method signature (Tupled arguments)
let output1 (a: int, b: string, c: double) =
  System.Console.WriteLine("output1: {0}:{1}:{2}", a, b, c)

// F# function (Curryable arguments)
let output2 (a: int) (b: string) (c: double) =
  System.Console.WriteLine("output2: {0}:{1}:{2}", a, b, c)

“output1” method is most popular standard .NET method. “output2” function is F# friendly the “Function.” If you write to call these members:

// Call the standard .NET method signature (Tupled arguments)
// (int * string * double) -> unit
output1 (123, "ABC", 456.789)

// Call the F# function (Curryable arguments)
// int -> string -> double -> unit
output2 123 "ABC" 456.789

The “Tupled arguments” method different for “Curryable arguments” function. We are finding for differents in AST nodes. Tupled arguments realize for SynExpr.Tuple AST node. But curryable arguments realize for nested SynExpr.App AST nodes.

It’s difficult that different on ASTs. I want to see AST structures. So I make smaller tool for dump AST nodes by F# Compiler Service (This tool for smaller hacks, very rough quality.)

This is output (Only contains interested fragments):

// For output1 method:
Ast.SynExpr.App( (* expr1 *)          // App - output1(...)
  ExprAtomicFlag.NonAtomic (* exprAtomicFlag *),
  false (* isInfix *),
  Ast.SynExpr.Ident( (* funcExpr *)
    output1 (* Item *)),
  Ast.SynExpr.Paren( (* argExpr *)    // Paren --> Tuple : Tupled arguments
    Ast.SynExpr.Tuple( (* expr *)
      [ (* exprs *)                   // Arguments declaring by list
        Ast.SynExpr.Const( (* [0] *)
          Ast.SynConst.Int32( (* constant *)
            123 (* Item *)));
        Ast.SynExpr.Const( (* [1] *)
          Ast.SynConst.String( (* constant *)
            "ABC" (* text *)));
        Ast.SynExpr.Const( (* [2] *)
          Ast.SynConst.Double( (* constant *)
            456.789 (* Item *)))],
      [ (* commaRanges *)]),
    range>.Some( (* rightParenRange *)))),

// For output2 function:
Ast.SynExpr.App( (* expr2 *)          // App - Apply 456.789
  ExprAtomicFlag.NonAtomic (* exprAtomicFlag *),
  false (* isInfix *),
  Ast.SynExpr.App( (* funcExpr *)     // App - Apply "ABC"
    ExprAtomicFlag.NonAtomic (* exprAtomicFlag *),
    false (* isInfix *),
    Ast.SynExpr.App( (* funcExpr *)   // App - Apply 123 for output2
      ExprAtomicFlag.NonAtomic (* exprAtomicFlag *),
      false (* isInfix *),
      Ast.SynExpr.Ident( (* funcExpr *)
        output2 (* Item *)),
      Ast.SynExpr.Const( (* argExpr *)
        Ast.SynConst.Int32( (* constant *)
          123 (* Item *)))),
    Ast.SynExpr.Const( (* argExpr *)
      Ast.SynConst.String( (* constant *)
        "ABC" (* text *)))),
  Ast.SynExpr.Const( (* argExpr *)
    Ast.SynConst.Double( (* constant *)
      456.789 (* Item *))))),

It’s contains too many verbose fragments so ugly (sorry). That means output1’s arguments declarates by list, output2’s arguments declarates by nested “SynExpr.App” (Apply argument).

That is the “Curryable arguments,” means:

// Call the F# function (Curryable arguments)
// int -> string -> double -> unit
output2 123 "ABC" 456.789

// It's meaning for expression:
((output2 123) "ABC") 456.789

// Split expressions:
let f1 = output2 123  // f1 is higher order function
let f2 = f1 "ABC"     // f2 is higher order function
f2 456.789

The AST structure different for smaller on original source codes. So we are sensitive for analyse by visitor pattern. This is just difficult…

# I have an idea from these things. I have a session for “You will be assimilated. Resistance is futile.” on “NGK2016B” LT conference (This is joke session :)


The filter’s middleware

So we can do anything by filter library AND implementation are very hard work. Then I’m thinking about how to do easier it…
I’m trying to design the helper library: “FSharp.Expandable.Compiler.Aspect.”

Will we have what intercept anything (by fscx)? This library is “Middleware” for fscx, we can safe implements for insert custom code between enter and leave function points. Today, we are calling the “AOP (Aspect oriented paradigm)” for this technics.

If we use this helper library by develop filter library in this case, our filter code are simple, safe and more easier.

For example below:

// This is "Aspect context" class
type SampleAspectContext internal (body: string) =
  // Finish aspect (trigger are leaved method with return value)
  member __.Leave(result: 'T) : 'T =
    Console.WriteLine("Leave: " + body)
    result
  // Finish aspect (trigger are leaved method with exception)
  member __.Caught(ex: exn) : unit =
    Console.WriteLine("Caught: " + body + ": " + ex.ToString())

// This is "Aspect" class
type SampleAspect() =
  // Start aspect (trigger are entered method)
  static member Enter(methodName: string, fileName: string, line: int, column: int, args: obj[]) =
    let body =
      String.Format
        ("{0}({1}, {2}): {3}({4})",
         fileName,
         line,
         column,
         methodName,
         String.Join(", ", args |> Seq.map (sprintf "%A")))
    Console.WriteLine("Enter: " + body)
    new SampleAspectContext(body)

This sample codes (Called “Aspect code”) are printing to console for function call message between enter and leave (include caught exception). “SampleAspect.Enter(…)” method is calling from before function call and “SampleAspectContext.Leave(…)” or “Caught(…)”
methods are calling from after function called.

“Enter(…)” method received the method name, source code file name, line/column number and will be calling with arguments. This sample codes prints these information to Console and create instance for “SampleAspectContext” class and return.

“Leave(…)” or “Caught(…)” handles to print leave messages. “Leave(…)” can receive for function called result. “Caught(…)” can receive for exception instance.

These aspect codes are truly “Duck-typed.” fscx will be identicate and not require any base classes and any interfaces. Because if request these types, we have .NET assembly referencing problem and fix this problem for very hard on packaging by NuGet :(

Ready for aspect named “SampleAspect” class. Next step, how to identicate this aspect for “FSharp.Expandable.Compiler.Aspect?” :

// This is declaring for aspect: "SampleAspect" class.
// Class name is free.
[<Sealed>]
type SampleAspectLoggerDeclaration() =
  // Construct with aspect class name. This is duck-typed naming (by string.)
  inherit DeclareFscxInjectAspectVisitor("SampleAspectLogger.SampleAspect")

Totally aspect tips: Aspect codes and this declaration requests loose types, so we can write aspects by C#.

This declaration contains on filter library, fscx can find and translate:

  • If fscx is targeting for a function, fscx insert custom AST nodes before calling function. Call to “SampleAspect.Enter(…)”
  • And fscx insert custom AST nodes after called function. Call to “SampleAspectContext.Leave(…)” or “Caught(…).” Leave with called function result value, Caught with exception instance by (inserted) try-with block.

This sample code fragment:

let f11 (a: int, b: string, c: int) =
  output1(a + c, b, 123.456)

Translate to:

let f11 (a: int, b: string, c: int) =
  let __arg_0 = a + c
  let __arg_1 = b
  let __arg_2 = 123.456
  let __context =
    SampleAspectLogger.SampleAspect.Enter
      ("f11", "SampleCode.fs", 2, 3, [|__arg_0, __arg_1, __arg_2|])
  try
    __context.Leave(output1(__arg_0, __arg_1, __arg_2))
  with
  | ex ->
    __context.Caught(ex)
    reraise()

This sample code declare “f11(…)” by tupled arguments method, sure can translate for function by curryable arguments.

Totally at this point: We have only the aspect classes “SampleAspect, SampleAspectContext” and aspect declaration “SampleAspectLoggerDeclaration”. These are contains no AST translation codes… Oh I made it! :)

Currently, I’m developing “FSharp.Expandable.Compiler.Aspect” now and not finish, because how to set where insert aspects to the target function.


Conclusion

fscx is challenging project for me. This project has several points:

  • fscx is easy-usage (by install NuGet) for any filter users.
  • We can develop any auto translation by ASTs (but very difficult).
  • We can develop easier any AOP based senarios by using fscx’s middleware.
  • All filter libraries can deploy by NuGet packaged.

The myth: We write OOP based codes by F# that’s hassle. Because if we become to FP, a little bit tired for declare many class and/or interface word elements. (It’s myth! don’t make seriously :)

We often write for UI codes, for example how to implement “INotifyPropertyChanged.” fscx is capable for can filtering auto-implement this interface, these senarios capsule to the middleware and running this code by no runtime cost. Finally we can construct ecosystem for filter libraries.

Acknowledgments: Thanks cooperation for On the road corporation and Mr. bleis.

Thank you for reading, welcome any comments! Happy christmas!!

できる!C#で非同期処理(Taskとasync-await)

asyncexception21ヤヴァいタイトル付けてしまった…. ええと、これはAdvent Calendarではありませんが、勢いで書いています(C# ADは既に埋まっていた…)

それには、こんな事情があったのです:

  • 「.NET非同期処理(async-await)を制御する、様々な方法」の記事がコンスタントにPVを稼いでいる
    (その割に、他の非同期関連の記事は読まれない)
  • asyncやawaitキーワードの使い方を度々聞かれる。
  • Task.Run()とか、Task.Factory.StartNew()とか、Task.Start()とか使ってるコードを頻繁に見る(不要なのに)。
  • Task.Wait()とか、Task.Resultとか使ってるコードを頻繁に見る(不要なのに)。
  • いまだにThreadクラス直接使ってるしかもその中でTask.Wait()とか(?!?!)

私のセッションや記事は、「なぜなのか」を深掘りして問題の根本を理解してもらう事を念頭に置いているのですが、さすがにこれだけ頻出してるのは、業界的に非常に問題があるんじゃないだろうかと思いました。特にXamarinや.NET Coreが注目されると、今までC#でコード書いた事が無い、と言う人が、見よう見まねで書き始める時期じゃないかと思います(それで何となく書けてしまうのが、C#の間口の広いところではあるのですが)。

もう一つ、現在、「非同期処理の必要性」のような記事を書いているのですが、まだ完成していないので、もうちょっとライトなやつを書いてさっさと公開した方が良い気がしてきました。

「できる(なんちゃら)」じゃないけど、そういう資料も切り口を丁寧に詰めればありかも知れない…

そういうわけで:

  • C#の非同期処理(Taskとasync-await)を使ってコードを書くなら、以下の事を知っていれば9割がたの問題は回避できる!!
  • ここで示した方法から外れたことをしようとしている場合は、殆ど間違いなく誤った記述をしようとしているので、もう一度何をしようとしているのか整理しよう!!
    (知っててやってる人はいいんです。こんな記事見る必要はありません)

章立てを2章だけにして、極限まで絞り込みました。ではどうぞ。

# 補足: F#erの人には説明は不要と理解しております :)
# コードはやや冗長に書いています。


1. 同期処理と非同期処理を対比させてみる

以下のコードは、ウェブサイトから「同期的に」データをダウンロードします。要するに、普通のコードです。WebClientクラスを使い、OpenReadメソッドでStreamを取得して、StreamReaderで文字列として読み取ります(クラス定義などは省略):

using System.IO;
using System.Net;

public static string ReadFromUrl(Uri url)
{
	using (WebClient webClient = new WebClient())
	{
		using (Stream stream = webClient.OpenRead(url))
		{
			TextReader tr = new StreamReader(stream, Encoding.UTF8, true);
			string body = tr.ReadToEnd();
			return body;
		}
	}
}

public static void Download()
{
	Uri url = new Uri("https://github.com/Microsoft/dotnet/blob/master/README.md");
	string body = ReadFromUrl(url);
	Console.WriteLine(body);
}

これを非同期処理として変形します。変形した箇所にはコメントを入れておきました。番号の順にやればスムーズに行くと思います:

using System.IO;
using System.Net;
using System.Threading.Tasks;

// Step5: メソッド内でawaitキーワードを使ったら、メソッドの先頭でasyncを加えなければならない。
// Step6: メソッド内でawaitキーワードを使ったら、戻り値の型はTask<T>型で返さなければならない。
//        T型は本来返すべき戻り値の型。
// Step7: 非同期処理対応メソッドは、「慣例」で「~Async」と命名する(慣例なので必須ではない)。
public static async Task<string> ReadFromUrlAsync(Uri url)
{
	using (WebClient webClient = new WebClient())
	{
		// Step1: OpenReadから非同期対応のOpenReadTaskAsyncに変更する。
		// Step2: OpenReadTaskAsyncがTask<Stream>を返すので、awaitする。
		//        awaitすると、Streamが得られる。
		using (Stream stream = await webClient.OpenReadTaskAsync(url))
		{
			TextReader tr = new StreamReader(stream, Encoding.UTF8, true);
			// Step3: ReadToEndから非同期対応のReadToEndAsyncに変更する。
			// Step4: ReadToEndAsyncがTask<string>を返すので、awaitする。
			//        awaitすると、stringが得られる。
			string body = await tr.ReadToEndAsync();
			return body;
		}
	}
}

// Step10: メソッド内でawaitキーワードを使ったら、メソッドの先頭でasyncを加えなければならない。
// Step11: メソッド内でawaitキーワードを使ったら、戻り値の型はTask型で返さなければならない。
//         ここでは元々返す値が無かった(void)なので、非ジェネリックなTask型を返す。
// Step12: 非同期処理対応メソッドは、「慣例」で「~Async」と命名する(慣例なので必須ではない)。
public static async Task DownloadAsync()
{
	Uri url = new Uri("https://github.com/Microsoft/dotnet/blob/master/README.md");
	// Step8: ReadFromUrlから非同期対応のReadFromUrlAsyncに変更する。
	// Step9: ReadFromUrlAsyncがTask<string>を返すので、awaitする。
	//        awaitすると、stringが得られる。
	string body = await ReadFromUrlAsync(url);
	Console.WriteLine(body);
}

# 補足: HttpClientではなくWebClientを使ったのは、対比させやすいためです。非同期前提で書くなら、始めからHttpClientを使うと良いでしょう。WebClientの代わりにHttpClientで同じ処理を書いてみるのは、良い練習になると思います。

asyncpractice1出来るだけわかりやすくなるようにコメントを入れたので、「ひたすらめんどくさそう」に見えますが、よく見れば大した作業ではない筈です。ReadFromUrlとDownloadのそれぞれのメソッドが、どのように変形されるのかを見てください。そうすると何となく法則が見えてくると思います。

また、この作業で、2つのメソッド「ReadFromUrl」と「Download」が、両方とも非同期メソッド化された事にも注意してください。

asyncpractice2つまり、特別な理由がない限り、非同期処理は呼び出し元から末端まで、「全て非同期メソッドで実装する必要がある」、と言う事です。ここで示した変形を行うと、連鎖的に、すべてのメソッドが非同期化されます。

これが核心です。以下にまとめます:

  • 非同期対応メソッドが存在する場合は、そのメソッドを呼び出すように切り替えます。上記では、「OpenRead」「OpenReadTaskAsync」に変えました。目的の非同期メソッドは、「TaskやTask<T>を返すメソッドがあるかどうか」や「~Async」と命名されているメソッドがあるかどうか、を目印に探すと良いと思います。また、この理由のために、メソッド名を「~Async」と命名する慣例が生きてきます。
  • TaskやTask<T>は、そのままでは戻り値T型になりません。これらの値は「await」キーワードを使う事で、戻り値を得る事が出来ます。awaitを実行している時、つまりそれが「非同期的に結果を待機している」と言う事です。
  • 自分で定義したメソッド内で「await」キーワードを使った場合は、メソッド先頭に「async」キーワードを追加する必要があります。「async」キーワードの効能はそれだけです!!! 以下のTask型云々は関係ありません。
  • 非同期メソッドの戻り値は、Task型、Task<T>型、または「void」とする必要がありますが、voidは殆ど使用しません(次章)。
  • 同期メソッドの戻り値の型がstringなら、Task<string>のように、ジェネリックなTask<T>型を使います。同期メソッドの戻り値がない(void)場合は、Task型(非ジェネリック)を返します。

これだけです。これだけですよ奥さん!!! 英語の動詞変形みたいな話です。この法則だけ覚えていれば、一旦同期的にコードを書いてみて、それから非同期処理に置き換えるという「練習」が出来ます。しばらくやっていれば、最初から非同期で全部書けるようになります。

ここまで、Task.Run()も、Task.Factory.StartNew()も、Task.Start()も、Task.Wait()も、Task.Resultも出ませんでしたね? これらを使う事はまずありません(断言)。

それでも、という方、では第2章へどうぞ。


2. 非同期操作のルーツ

非同期操作が考案された由来、とかそういう話ではなく、「どこからが非同期処理として扱われるか」あるいは「どこからを非同期処理として扱うのか」、という話です:

// これはWPFのウインドウ実装です(但し、WPFであることはあまり重要ではありません):
public partial class MainWindow : Window
{
    // 参考:
    //   Window.Loadedイベントの定義:
    //     public event RoutedEventHandler Loaded;
    //   RoutedEventHandlerデリゲートの定義:
    //     public delegate void RoutedEventHandler(object sender, RoutedEventArgs e);

    // コンストラクタ
    public MainWindow()
    {
        InitializeComponent();

        // ウインドウ表示開始時のイベントをフックする
        this.Loaded += MainWindow_Loaded; 
    }

    // ウインドウが表示されると呼び出される
    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        // 第1章で実装したReadFromUrlを呼び出して、テキストボックスに表示する
        Uri url = new Uri("https://github.com/Microsoft/dotnet/blob/master/README.md");
        string body = ReadFromUrl(url);
        textBox.Text = body;
    }
}

# XAMLは省略します。TextBoxコントロールに”textBox”という名前を付けて定義してある前提です(また、本当はデータバインディングでやるべきです)。

このコードだと、ダウンロードが完了するまで、UIが固まってしまいますね。では、これを非同期処理に対応させます:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        this.Loaded += MainWindow_Loaded; 
    }

    // Step3: メソッド内でawaitキーワードを使ったら、メソッドの先頭でasyncを加えなければならない。
    // Step4: メソッド内でawaitキーワードを使ったら、戻り値の型はTask型で返さなければならない。
    //        しかし、Loadedイベントのシグネチャは変更できない。従って「void」のままとする。
    // Step5: 非同期処理対応メソッドは、「慣例」で「~Async」と命名する。
    //        しかし、値を返さないためawait忘れの可能性はない事と、あくまで慣例であるため
    //        ここでは「~Async」と命名しない。
    private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        Uri url = new Uri("https://github.com/Microsoft/dotnet/blob/master/README.md");
        // Step1: ReadFromUrlから非同期対応のReadFromUrlAsyncに変更する。
        // Step2: ReadFromUrlAsyncがTask<string>を返すので、awaitする。
        //        awaitすると、stringが得られる。
        string body = await ReadFromUrlAsync(url);
        textBox.Text = body;
    }
}

asyncpractice31MainWindow_Loadedメソッドと、第1章のDownloadAsyncメソッドとの違いを確かめてください。Loadedイベントのシグネチャは「RoutedEventHandler」デリゲートであり、「delegate void RoutedEventHandler(object, RoutedEventArgs)」です。つまり、戻り値を返さない「void」であることが決められています。

非同期メソッドはTask型かTask<T>型を返すべきですが、ここでは型が合いません。値を返さない場合に限り、このような非同期メソッドを「void」で定義します。

ポイント:

  • 戻り値を返さず「void」と定義するメソッドは、イベントをフックしたハンドラメソッドのような場合しかありません。戻り値をvoidとした場合、呼び出し元は非同期処理の結果を追跡できず、処理結果を感知しません。
  • 逆に考えると、Loadedイベントの場合、そもそもイベントの呼び出し元は非同期処理の結果など気にしていません(voidなので)。だから、voidとしても良い、と解釈することもできます。
  • 「Task」を返そうが「void」を返そうが、「await」キーワードを使ったので「async」キーワードを追加する必要があります。

(何らかの)処理の開始位置が、このハンドラメソッドにある事にも注目してください。このように、呼び出し元(WPFのLoadedイベント処理)が、呼び出し先(MainWindow_Loadedメソッド)の事について感知していないような場合にのみ、呼び出し先の非同期メソッドの実装方法を考える必要があります。

コンソールアプリケーションの非同期処理ではどうでしょうか:

// コンソールアプリケーションのメインエントリポイント
// awaitを使っているのでasyncを追加する(?)
public static async void Main(string[] args)
{
    // Task型が返されるので、この処理を待機するためawaitする(?)
    await DownloadAsync();
}

asyncpractice4第1章のDownloadAsyncメソッドを呼び出してみました。もし、この呼び出しをawaitで待機する場合、Mainメソッドの先頭にasyncと付ける必要があります。このコードは問題なくコンパイルできますが、実行すると(殆どの場合)ダウンロードが完了することなくプログラムが終了します。

Mainメソッドは、DownloadAsyncメソッドの非同期処理をawaitで待機します。しかしこれは「非同期的に待機」しているので、「Mainメソッドの処理(メインスレッド)は継続的に実行されている」のです。すると、メインスレッドはそのままMainメソッドを抜けてしまい、一瞬でプログラムが終了してしまいます。

したがって:

public static void Main(string[] args)
{
    // Step1: Task.Wait()を使わざるを得ない。
    Task task = DownloadAsync();
    task.Wait();
}

asyncpractice5このような場合にのみ、Task.Wait() 又は Task.Resultで待機する必要があります。GUIアプリケーション(WPF/UWP/Xamarinなど)を実装している皆さんには、まったく縁のない話なので、このことは忘れても構いません。GUIアプリケーションでTask.Wait() や Task.Resultを使うとデッドロックを起こします。

大事な事なのでもう一度: 「デッドロックします。使ってはいけません」

今Task.Wait() や Task.Resultを呼び出してうまく動いていますか? そうですか、いつかTaskに「後ろから刺される」覚悟はしておいた方が良いでしょう…
awaitで待機するように記述する事を「大前提」として、そうするにはどうやって書いたらいいかを考えると良いと思います。


まとめ

これだけです。とりあえずこれだけで、「マトモ」な非同期処理コードを書くことが出来るはずです。繰り返しますが:

  • C#の非同期処理(Taskとasync-await)を使ってコードを書くなら、これらの事を知っていれば9割がたの問題は回避できる!!
  • ここで示した方法から外れたことをしようとしている場合は、殆ど間違いなく誤った記述をしようとしているので、もう一度何をしようとしているのか整理しよう!!

これを読んで新たに疑問が湧いてくるかもしれません(例えば、どうしてデッドロックするのか、voidの非同期メソッドの結果はどうなるのか、など)。それは良い事です。そのような場合は、より深く解説した記事を読んでみてください。

それでは!