F#は世界を支配する
サーバーサイドのプログラミングでは何のしがらみもなくF#を使われていることと思いますが、クライアントサイドではM○様に愛されていないのかいまいち不遇な扱いを受けてきたような気がするF#ですが、ここ最近ちょっと事情が変わってきた気がします。
なにやらコンパイラーにバグがあったかなにかでPortable Class Library(以下PCL)をF#で作っても、他のPCLから使えないとか他のPCLとリンクできないとかそれを使ったストアアプリは審査に落ちるらしいとか言われてきて、どうなってんのよM○さん!と悶々としていましたが3.1になったあたりからその辺が改善されてきたようです。
そしてちょうど歩を合わせるかのようなXamarinのPCL対応!これでネイティブアプリとしてのF#の使用の道が大きく開かれそうです。
またネイティブアプリだけじゃなくHTML5へもWebSharperなどのJSへのトランスレーターを使えば対応可能なので、これはもしかしたらF#で全世界征服ができるんじゃないかという気がしてまいりました。
今回もともと.NET3.5のWindowsForm環境向けに作ったUI含むビジネスコンポーネント?をXamarinとHTML5でとりあえず稼働させることができたのでその辺の話をしたいと思います。
ものとしては株など向けのいわゆるチャートです。後ろでリアルタイムに値段を受けて並列に内部でゴニョゴニョ計算しつつ、UI側でグラフ表示をしたりユーザーのマウス操作などで表示する位置、内容を変えたりトレンドラインなどをマウスで引いたりします。ビジネスコンポーネントとしてはまぁそれなりの複雑さで一通りの動作をしてるようなものではないかと思います。
.NET3.5向けに作っていたもののPCLへのコンバート、ストアアプリとしての稼働。
たぶんWindows8が出る前後で実験的にストアアプリで動かせるかどうか試したあたりが最初の動きです。ご存知のように(いまだに)ストアアプリの開発ではF#はPCLとして実装したものをC#やVBから参照する使い方しかできないため必然的にPCLにする必要がありました。
もともとFormだけでなくWPFで使ったりすることも視野に入れていたためコンポーネントに対してのマウス操作などのイベント、コンポーネントからの描画の実行は抽象化していました。
マウスなどのイベントは内部定義のマウスイベントに変換した上でそれを渡されて処理、描画などはFormのGraphcisを模したインターフェースを定義して、そこで定義されているDrawRectangleなどの命令を実行すると。WPFなどではそれを受けてシェイプなどを作れば行けるのかなと。
もともとそのような作りをしていたのでストアアプリとしても具体的部分をC#側で実装してF#側にインジェクトすれば行けるだろうという目算で強引にPCL化を行いました。
手順としては
- 既存のプロジェクト名に"PL-"などのプレフィックスをつけたPCLのプロジェクトを作成、既存のプロジェクトにあるソースをコピーしてプロジェクトに追加。
- 出るエラーを片っ端から潰していく。PCL上では実装できないものはインジェクトするように抽象化して外出しする。
というように気合です。たぶん軽くとりあえず2-300以上のエラーが出ていたと思いますが、潰していけばいつかはなくなります。正直一年以上前だったのでうろ覚えですが、Threadをnewする方法がなかったりSerializationとか結構普通に使っていたものが使えなかったりで途方に暮れた気もしますが、ないものはあきらめる、とにかく外出しにするで潰していきます。
間違ってる所もあるかもしれませんが、PCLにないものとしてメモ書きしたものがあったのでとりあえず乗っけておきます。
- ThreadLocalStorageがらみ
- Stopwatch
- Conosle
- StackTrace
- MarshalAs
- System.Data
- Assembly.GetEntryAssembly
- IDeserializationCallback
- System.Convert.ChangeType
- Type.Findinterfaces
- Microsoft.CSharp
- SerializableAttribute
- System.Runtime.Remoting
- System.IO
- AsyncResult
- ThreadPriority
- BindingFlags.GetField
- Type.GetMethodのオーバーロード
- System.Xml
- System.Threading
そんな感じで気合でつぶしていき、ストアアプリ側で具体的実装をしたものをインジェクトした結果、一応ストアアプリとして動くようになりました。たぶんこの頃はF#のPCLを使ったアプリは審査ではじかれていたころだと思いますが、実験プロジェクトなので(゚ε゚)キニシナイ!!
PCL化したもののXamarinでの稼働
とりあえず動かしたけれどストアアプリの案件などなく、忘れ去られてたPCL化コンポーネントですが、先日のF#のPCLまとも対応、XamarinとM○様の提携によるPCLの本格的サポートで急に日の目を見ることに!
iOS、Androidでプロジェクト作成、そこからVSでビルドしたPCLをアセンブリ参照するようにして、ストアアプリと同じように具体的な実装をiOS、Android側で行いF#にインジェクトします。
正直まだXamarinStudioなどがこなれていなく動かすのには結構トラブルが。といってもそのトラブルはほとんどXamarinStudioなどのバグや自分が不慣れなことに起因しており、PCLとそれに対して具体的実装をインジェクトする方式はかなりあっさり動きました。これでF#が25億デバイスの上で動く道が!しかもワンソース、ワンバイナリー!
今回のものだと98%ぐらいはソース共有できてます。素敵!!!
XamarinではPCLだけでなくF#だけで全てのアプリが書けるらしくM○様以上の厚遇ぶりがとても魅力的です。
今回はPCLをアセンブリ参照しましたが、PCLのライブラリーをプロジェクト参照としたほうがデバッグなどで幸せになるのではとは思います。
ストアアプリではDrawRectangleなどでそのたびにシェイプを作っていたためちょっと遅めな気がしていましたが、iOS、Androidではサクサク動いています。
WebSharperでのHTML5化
そんなチャートをHTML5でも動かしたいという話が出てきたんですが、またjavascriptとかTypeScriptで実装しなおすとか正直いやです。いくら元のソースがあるにしても同じようなものをまた実装するとかだるすぎます。
そんな貴方にWebSharper!WebSharperはこれも去年ぐらいに一度サンプルとして使ってみて情報がかなり乏しいとか非常に不安はあるものも、物自体はかなり安定して変換できあっさり動いた記憶がありました。
FunScriptも検討はしたのですが、一部のソースを切り出して試したところ、
- コンパイラ(トランスレート?)エラーで変換できない。
- エラーも変換の途中で例外が出て何がどう悪いのか調べるのきつい。
- 生成されたソースも変数名とか何やら番号付きのが生成されて正直追うのが無理と心がおれた。
などで敢え無く却下。
WebSharperで同じ部分を試したところ
- だいぶあっさり変換できる。
- エラーもどこが悪いのか追えるレベルでエラーメッセージ出してくれる。
- 変換されたソースも変数名やメソッド名などで該当箇所の見当がつくので何とかなりそう
と実際に使えそうな感触。さすがお金をとるだけある(´・ω・`)(商用利用などでなければ無償で使えます)
WebSharper自体よくわかっていないためLibraryとしての作成の仕方がわからずSiteletHtmlAppプロジェクトを作ってそこにソースを追加することに(手抜きですみません…)今回は上のPCL化したものとソースを共有することが目的なのでソリューションにPCLのライブラリーとストアアプリのプロジェクトも追加してSiteletHtmlAppプロジェクトにはPCLのライブラリーの下のソースをリンクとして追加します。WebSharperではjsとして変換する部分には[
この属性をつける必要があったりjsへの変換上、数値の演算などでWebSharperで定義しているEcmaScriptで定義されているもの(EcmaScript.Math.Minなど)をつかわないといけないようなので#if WEBSHARPER などとしてWebSharper用の記述とそのほかのものを分けることにしました。のでちょっと不細工に…まぁここはうまくまとめれば最小限の差異とできると思います。
結果SiteletHtmlAppプロジェクトのプロパティのBuildタブのConditional compilation symbolsにWEBSHARPERと追加、同じソリューションにあるストアアプリのプロジェクトには何もつけず双方で適切なソースがビルドできるようにします。両プロジェクトを同じソリューションに入れてビルドすることでWebSharper向けのソースの変更がストアアプリのプロジェクトを壊したらすぐチェックできるようにします。
このようにしてプロジェクトを作った後ビルドしたらてんこ盛りのエラーが…徐々にプログラムを書き足したのではなく既存で動いてるソースを一度に追加したため部分的に動くかどうかも確認取れずその先に未来があるかわからない状態でエラーを潰していく羽目になり心がおれそうになります。ほかの仕事をしながらやっていたので全部潰すのにおそらく2,3週間以上かかりました。これができたらAdventに参加しようとしていたのですが、結局間に合わず延長戦に…
途中で起きたエラーをメモ書きしていたのでとりあえずそのまま貼り付けます。
- type Hoge() as this=...とすると error FS0452: Quotations cannot contain inline assembly code or pattern matching on arrays が出るっぽい http://www.rqna.net/qna/ptqtpm-f-quotations-arrays-and-self-identifier-in-constructors.html
- inner generic functionだめ。これは型を指定するか外出しにすればいい。 Inner generic functions are not permitted in quoted expressions. Consider adding some type constraints until this function is no longer generic.
- object expressionsだめ。クラス定義する。
- printf/sprintf,string.Format使えない。めんどくさい。
- DictionaryなどF#のコレクションに変える
- パターンマッチで配列使うと Quotations cannot contain inline assembly code or pattern matching on arrays が出る。
- for x in xxx do...のxxxがarrayだと Quotations cannot contain inline assembly code or pattern matching on arrays が出る。
- やはりEvent使えない模様。ビルドは通ってjavascriptの生成はできているようだが。Eventを適当に定義←これは結局動いたっぽい。なんで問題になった?
- System.Typeなど使えない。→これは使わないように何かでしのぐ
- 継承してもしこのタイプだったらとかやってる所→もともとの継承構造をunionに置き換えるか、それが難しそうなら継承したものを持つunionを定義してそれを持つようにする。
- let triMarg,triSize,r3=4.0,5.5,sqrt 3.0 的なのは各々別の行に。
こんな感じのエラーをいろいろググりながら潰していきます。ここでWebSharperの情報が少ないことがボディブローのように聞いてきます。英語必須です。FPishなどで質問してもなかなか返ってきません…
そんなこんなですが、なんとかエラーを潰すことはできました。
やっと今までと同じようにインジェクトする具体的な部分の実装です。今回は描画などはWebSharperで定義されているCanvasを受けてそこに描画するような実装になります。
具体的な実装を終えて動かしてみると…動きません。のでChromeのデバッガーにお世話になります。自分はjsは詳しくないのでググりながらデバッガーを何とか使っていくことに。
コンソールに出るエラーをもとにそこのjsを調べてそれをもとに該当箇所のF#のソースを見て何がおかしいのか見当をつけていきます。
WebSharperの中の人は「僕はWebSharper使うとき生成されたjavascriptなんか見ないよ、見る必要ないよ、ハッハー(意訳)」といわれていましたが、やはりおかしいときにはどこがどうおかしいのか生成されたjavascriptを検証しなければなりません。
が、変換されたjsは前にも言ったようにメソッド名とか変数名が残っているので追うのはそこまで難しくありません。
そんな中で次のような生成エラー?などにはまりました。
- interfaceで定義したメソッドをクラスの方から同じメソッド名称で呼ぼうとしたら自分自身呼び出しのコードになってスタックオーバーフロー
[F#]
type Series<'a>()= interface ISeries<'a> with member this.Count=_values.Length member this.Count=(this:>ISeries<'a>).Count
[javascript]
get_Count:function() { return this.get_Count(); },
- 引数として配列を渡すものと値を一つだけ渡すオーバーライドしたメソッドを呼び出すコード、メソッドとしてはXXXとXXX1とあるが呼び出し側で正しくXXX1を呼んでくれなかったのですっ飛んだ。
- module内やクラス内のmutableをラムダなどから参照したときにほかの更新がうまく取れない?refに変更して回避。
- async try-catch内で例外が起きた場合デバッガーで止まってくれない? WebSharper.js-704にブレークしかけることで判明。
WebSharperは大分安定してソース変換してくれますが、やはりうまく変換してくれない?部分が出て来ました。そこら辺はうまく書き方を変えてしのぎます。
また今回のPCL化したものは一部C#の部分を含んでいたためその部分はF#で簡易実装してとりあえず動くようにして、それをもとにjs化しています。
そんなこんなでゴニョゴニョを繰り返してやっと先日、js化したものが動きました。∩( ・ω・)∩ ばんじゃーい。一部簡易実装したところはあるものの元はフルに.NET向けに実装したものと(ほぼ)同じソースがそのままHTML5でも動く!素敵!!!
今回はC#で作ったりクラス継承などしてる部分などもあったため手間がかかってますが、最初からF#でF#らしく作っていたらたぶんもっと簡単だったでしょう。
感触的にはビジネスアプリ的なものならば、Form、WPF、ストアアプリ、iOS、Android、WindowsPhone、HTML5の全てのプラットフォームに対してワンソースで対応することは無理せずに可能だと思えます。
サーバーだけでなくクライアント側の数十億のデバイスで動かせるものをワンソースで対応できる…もうこれはF#ですべてを制御できる、支配できるといっても過言ではない!
これまで
「そんな言語で大丈夫か?」
といわれてきたかもしれないF#ですが、来年からはそういわれたらこう返しましょう。
「 む し ろ F# で 書 け 」
来年もF#がより素晴らしい言語となることを願ってこの辺で締めたいと思います。長々とお付き合いありがとうございました。