読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

Comm Tech Festival D-3 「open FSharp」 (続きはWebで)

.NET F# 勉強会 ポエム

@ufcpp さんにお話しをいただいて Comm Tech Festival に参加してきました。ありがとうございました。 comuplus.doorkeeper.jp

「open FSharp」というタイトルで発表させていただいたのですが、時間配分がうまくいかずにスライドの最後まで紹介することができませんでした。申し訳ありません(はずかしい)。

「続きはWebで。」とお約束をしていたので、こちらにまとめます。使用したスライドをそのまま公開することも考えましたが、それだと内容が伝わりにくくてあまり価値がなさそうかなと思ったのと、変な誤解をされる恐れもあると考えたので、発表時に話した内容に簡単な補足を加えたり、ときにはざっくり省いたりしたかたちでこちらにまとめることにしました。


時間オーバーしただけあってだいぶ長いです(ポエム)。
セッションを聞いていただいたかたは、続きのみどうぞ

f:id:zecl:20150927154047j:plain
f:id:zecl:20150927154123j:plain

@mayukiさんが作ってくれた画像。あまり深くは触れませんが、IQ145 がだいぶやばいです。別に殺伐とはしていなかったはず。

f:id:zecl:20150927154337j:plain
基本的には、F#が"ふつうに関数型言語である"というありがたみのない話

f:id:zecl:20150927154435j:plain
f:id:zecl:20150927154509j:plain
言語遍歴としてはVB(レガシー的なのも含む)、C#Java、あとはDelphiあたり。その他いろいろさわってますが、よく知られている(一般的な)オブジェクト指向言語をつかって仕事をしていることが多かったです。
かつて関数型言語関連の研究をしていたとか、数学が得意だとかでもなく一般の人。

C#の会社に所属していながら、F#の話をする。どうなんだ?という感じですが、理解ある会社、同僚たちと働かせてもらえてありがたいことです。


f:id:zecl:20150927154644j:plain
弊社開発者、結構 F# インストールしてくれています。使ってくれている(or 今後使ってくれる)かどうかはわかりませんが。 こちらのツイートをみたとき、まゆきさんツンデレだなあと思いました。

f:id:zecl:20150927154943j:plain
自宅でのVSのデフォルト環境は F# にしてます(だから何)

f:id:zecl:20150927155131j:plain
・FsBulletML
むかし一世を風靡した(?)弾幕記述言語BulletML(ABA Games)のF#実装。
特徴としては、判別共用体による型付き内部DSLXML、SXML、FSB(独自形式)の3種類の外部DSLを備えていることです。
また、各外部DSLから内部DSLへのコンパイル時変換を可能とする FsBulletML.TypeProviders も提供しています。NuGetである程度DLされているんだけど、使ってみたという話を一度も聞いたことないですね。

・UniFSharp
Unityエディタ上からMSBuildを実行してF#をビルドするやつ。
疑似的にF# ScriptをインスペクターにD&Dできたりします。あと、無駄にUnityちゃんがしゃべります。Unity で F# がサポートされるようになる日は、まずこなさそうですね。

f:id:zecl:20150927155515j:plain
F#に対して「無関心」だった人に関心をもってもらいたい。F#に対してもともと「関心」があった人をより協力者に近づけたい。というのを目標(目的に)

>どうして F# を流行らせたい?
単純に良いと思うものは広めたいし。語り合える仲間が増えるのはうれしいよね!というのは当然として、ユーザーが少ないことは問題でしかないと考えるからですね。実際ユーザが少なくてイイことなどまるでないので。開発者が少ないということは、ライブラリやフレームワークの選択肢の幅も狭くなります。書籍も出版されにくいし、Web上でも情報が少なく何かトラブルがあったときに困る可能性があります。んなもんで、ビジネスにも適用されにくくなります。とかいう、ごくふつうの話

f:id:zecl:20150927160039j:plain
実はVisual Studio 2010からいました

f:id:zecl:20150927160142j:plain
MSRとして、ML(Meta Language)的なアプローチを使った言語設計で研究することが決定したのが2002年らしいので、F#1.0に至るまで約3年かかったということになるのかな。
F#の父 ドンちゃんこと Don Syme 氏は、1998年からマイクロソフトで働き始めて.NET CLRC#(VB) のためのジェネリック機構を作った人。すごい人 #実際すごい

F#の特徴的な機能としては、sequence expression(シーケンス式)、非同期ワークフロー、computation expressions(コンピュテーション式)、Type Provider(型プロバイダー)などの新しい(面白い)試みがある。小さなところでいうと、Active Pattern,(アクティブパターン)なんてのもある。簡単に言うとユーザ指定形式のパターンマッチ。

最新版のF#4.0は、3.0からの大きな機能拡張はなくて割と地味な印象がある。でも、細かなバグの修正だったり手触り感の向上があって普通に便利になりました。あと、デフォではVisual Studioでインストールされなくなりましたが。このあたりは別にどっちでもいいです、

f:id:zecl:20150927160739j:plain
他には、毛色が違うけどリアクティブプログラミングもパラダイムのひとつと言って差支えないかな。

F#は関数プログラミングに主軸を置いたマルチパラダイム言語です。もっと言うと、F# は .NET で 実用レベルで使用できる関数プログラミングパラダイムをサポートしている唯一の言語。それ以上でもそれ以下でもないです。

C#(VB)はオブジェクト指向プログラミングを主体としたマルチパラダイム言語。とはいっても、C#(VB)のそれは関数プログラミングを支援している関数型プログラミング言語に比べて、ごくごく限られていて。C#(VB)でできるプログラミングのパラダイムの1つに関数プログラミングを加えるは、ちょっと違うかなと(個人的に)は思うところです。もちろん、LINQ使用時のラムダ式の利用など関数プログラミングのエッセンスが効果的にとりいれていて、それはオブジェクト指向プログラミングとも非常になじんでいます。

f:id:zecl:20150927161826j:plain
Monoがあることによって、いろんなところで動くよ
他には、GPUとかブラウザ上とか。さまざまな環境で動くF#さん

f:id:zecl:20150927161940j:plain

Fsharpbindingプロジェクトってのがあってですね・・・いろんなエディタいろんな環境でつかえるよ。 https://github.com/fsharp/fsharpbinding

でも、いちばんのオススメは、やはりVisual Studio(現時点ではおそらく最強)。Visual F# Power Tools 拡張機能の充実ぶりがすばらしいので。
一応条件付きとはいえ、Community Editionでフルに使えますし #いい世の中になりました

C#で作られたライブラリも利用可能、とは言ったものの、F#から使いやすいC#ライブラリとして設計されていないと、だいぶツライ感は否めないですけど。はい。

f:id:zecl:20150927162108j:plain
Visual F# Power Toolsの機能をいくつか紹介しました。

f:id:zecl:20150927162153j:plain
C#VBと同等にXMLドキュメントを生成してくれるやつ

f:id:zecl:20150927162408j:plain
クイックサーチ機能ですね

f:id:zecl:20150927162415j:plain
VS標準ではfsproj内のフォルダ作成はサポートされていないので、 フォルダをきって、階層構造をつくれるようになるのはふつうにうれしい。

f:id:zecl:20150927162426j:plain
これまではインターフェイスの実装は、すべて手で記述する必要があった…(圧倒的作業感) 自動生成はありがたい(実際助かる)

f:id:zecl:20150927162635j:plain
レコード型のスタブを生成する機能

f:id:zecl:20150927162706j:plain
判別共用体のパターンマッチケースを網羅的に自動生成してくれるやつ(こちらも助かる)

f:id:zecl:20150927162749j:plain
必要な名前空間やモジュールを追加してくれるやつ C#でいうところの、必要な using ほげもげ を追加してくれるやつ

f:id:zecl:20150927162844j:plain
発音はプラジナでよいのかな



f:id:zecl:20150927163121j:plain
githubですでにα版が公開されたので、興味のある人は見てみたらよいんじゃないでしょうか #か msrccs.github.io www.nuget.org

f:id:zecl:20150927163317j:plain
プログラミングスタイルと、それの支援状況的なアレ

f:id:zecl:20150927163421j:plain
関数型言語関数プログラミングなどは定義が結構あいまいで説明が難しい

f:id:zecl:20150927163612j:plain
おおむね間違っていないと思うが、個人的な見解と雑さがやばい

副作用 is 何 という話もあるが、深入りはやばい #実際やばい ので、ここでは破壊的代入操作や、コンソールや画面描画などの出力を伴う操作ということでお茶を濁す

f:id:zecl:20150927163710j:plain
高階関数の扱いやすさが、 関数型言語っぽさに比例しているかなあと、個人的には考えます。まぁ、関数型言語かどうかを端的にあらわすのであれば、ラムダ計算に基づいているかとかで判断するほうが妥当なのかなあ? 最近のプログラミング言語は、マルチパラダイム化(というか、関数プログラミングの一部の要素が取り入れられる)が進んでいてその境界が割と曖昧になってきている。が、関数プログラミングというスタイルについて 言語(コンパイラ)や開発環境でどの程度支援されているか(推奨されているか)どうかで、関数型言語と呼んで差支えがないかどうか判断をする感じかなあ。

f:id:zecl:20150927163841j:plain
これ。関数型妹botかなんかが流行っていた時期につぶやいたものだったかな? オブジェクト指向プログラミング言語は、オブジェクト指向プログラミングがなんたるかを教えてくれない。 では、関数型言語はどうだろう? かなりのぶぶんで言語そのものから関数プログラミングのスタイルを学ぶことができる。 ある面では学習コストは高いけど、そういった面で考えると、頭の中がまっさらならむしろ学習コスト低いのかななんて。

理解するのに時間がかかるような難しい理論や概念ももちろんありますが、大部分が誤解かと思います。関数型言語を使って関数プログラミングを学ぶ、その入り口自体には難しい要素はほとんどなく、シンプルかつ堅牢なルールが重んじられる(ことが多い)ので複雑性が少なくなって、むしろ簡単とさえ感じる部分もあるくらいです。少なくとも、可変な値に翻弄される複雑性を減らすことができるので、書きたいプログラムそのものに集中できる時間が増える。ということは実際に利用してみると実感できるはずです。

ただし言語によっては大きくシンタックスが異なる場合があるので、ある程度の抵抗感が生まれてしまうのは事実(しょーがない)。F#も「見た目がきもくて無理」なんて言われることもある。好んで使っているひとは「美しい」って言う人多いのに。

F# は(いい意味で)いろいろとゆるいので。みゅーたぶるな値も使い放題だし、手続き的だったりオブジェクト指向なスタイルを残しつつ、少しずつ関数プログラミングのスタイルを取り入れていくことが無理なくできるのではないかな。関数型言語のなかでも、とりわけ難しくない方ではないかなあと思う(個人の感想)

f:id:zecl:20150927164342j:plain
だいぶ釣りだし、実際のところ1分じゃぜんぜん足りない(そりゃそうだ)

某F# for fun and profit という素敵サイトの記事から引用したものです
fsharpforfunandprofit.com
重要なところも割とさっくりと大胆に省略されていたりしますが、まあイサギヨサがある。

そもそも、F# のコードを見たことがない人も多いのでは?ということで入れました。
かなりさらっとやるつもりでいたんですが、ここにかなりの時間を費やしてしまったので時間切れになってしまった。

f:id:zecl:20150927170525j:plain
let キーワードで 不変な値に名前をつけて定義します
明示的に値の型を記述していないこと注意してください(型はコンパイラが推論してくれます)

f:id:zecl:20150927170554j:plain
F# のリストは、順序が指定されていて変更不可の一連の同じ型の要素を表します。

リストを定義するには、セミコロンで要素を区切って角かっこで囲む感じ。
単一要素とリストの結合はコロン2つで。リスト同士の結合は@で。

f:id:zecl:20150927170705j:plain
F# では 返り値を返すときに return キーワードを使用しません。常に関数内の最後の式が返される。
最初こそ少しとっつきにくいかもしれないが、シンプルなルールなのですぐに慣れることができるだろうし、実際明確でわかりやすい。

f:id:zecl:20150927170754j:plain
ふぃぼなっち

f:id:zecl:20150927170839j:plain
リストから偶数のみを取り出す関数
偶数かどうかを判断する isEven が関数内関数として定義されている

リストの対する操作は Listモジュールに定義されているので、よしなに使う。
List.filterはLINQでいうところのWhereのこと。

f:id:zecl:20150927170944j:plain
1から100までのリストについて、正方形の面積を求めてその和をだす(なんのために)

関数に適用される引数を明確にするためには括弧を用います。
この例に関していうと、括弧を書かないとコンパイルが通らないので。

f:id:zecl:20150927171103j:plain
正確には前方パイプライン演算子

F#ではコードを書きやすく、また見通し良くするためのパイプライン演算子が標準で提供されている。

f:id:zecl:20150927171138j:plain
無名関数(ラムダ式)を書くときは fun キーワード使う。 もしかすると楽しい気分を演出するかもしれないし、しないかもしれない

f:id:zecl:20150927171333j:plain
完全に一致(ではない)が、似ている。

F#ではループの代わりとして利用できる高階関数が数多く提供されている。ので、ループしようと思ったら再帰関数書かなきゃだめなの!?みたいな変な誤解は持たないでほしい。C#(VB)のLINQになじみがあれば、LINQ to Objectsを使えば for や foreach などのループ用の構文を必要としない場面が多いということはわかってもらえるはず。

f:id:zecl:20150927171612j:plain
LINQの場合は当然、あらかじめ定義されている拡張メソッドなりがある場合のみにメソッドチェーンとして処理を手続き的に書き下すことができる。それに対してパイプライン演算子によるチェーンでは、型さえ合えば自由に関数をチェーンできる。果てしなく。
ただ、後ろになんでも繋げられることをメリットととらえるか、はたまたデメリットととらえるか。どちらも正しい。これは、開発のスタイルや重視するプログラミングパラダイムによって意見が変わってくるところだろうと思う。関数プログラミングを主体とするF# においては、パイプライン演算子によって後続の処理を好きなだけ連ねることができるのはメリットでしかない。

f:id:zecl:20150927171906j:plain
3年前のツイートだった

わたしはこれでF#覚えました。これは別に冗談で言っているのではなく結構本気で。(|>)パイプライン演算子という中置演算子を用いることで、関数と引数の順序を逆にすることができる。これによって、関数適用の流れを手続き型的に書き下すことができるので、処理の流れがわかりやすくなります。F#ではこの「パイプライン演算子を使う」ことが基本でありながら、同時に最上級のプラクティスであると言っても過言ではなく。これを好んでよく使うことはおのずとF#の上達に繋がります。

f:id:zecl:20150927172312j:plain
複数の値を組みにするやつ。
雑に言うと、フィールド名を持たない無名クラスのようなもの

f:id:zecl:20150927172348j:plain
System.Tuple.Create<,>とか書かなくてよいし、Item1,Item2…と付き合わなくてもよいという話。
多くの C# 開発者は タプル扱いやすく改善されないかなぁと思っているはず。 ちなみにF#のそれ。この場合かっこ(パーレン)も省略できる
あ、匿名型は F# にはないです。 F# でもたまーに欲しいシーンはあるので、ちょっと欲しいです。

f:id:zecl:20150927172657j:plain
match式は雑にいうと、 switch 文の強力版

ただし、match式はパターンマッチのほんの1例にすぎない。この例だと、あまりありがたみはないが、F#にはさまざまな形式のパターンマッチが提供されているので、非常に便利。

f:id:zecl:20150927172823j:plain
雑に言うと、タプルの各要素(フィールド)に名前つけたやつ 名無しの権兵衛さん

f:id:zecl:20150927172852j:plain
レコード型をパターンマッチで分解しているの図。 突然の and !だったりするが華麗にスルーで。

f:id:zecl:20150927172959j:plain
リストもパターンマッチで分解できる例

他にも、ユーザ指定形式のパターンマッチであるところの、Active Pattern,(アクティブパターン)なんてのもある。

f:id:zecl:20150927173044j:plain
なんか見たことあるやつ。動物抽象クラスを継承して動物たちが鳴くやつ。

f:id:zecl:20150927173206j:plain
突然の function式 ! だが、ここもスルーで(機能的にはmatch式と同じでその引数省略できる版)

いわゆる代数的なデータ型的なやつ。一部誤解を与えてしまう可能性もあるが、ある種のクラス階層を表現するのに使える。
オブジェクト指向プログラミングの「継承」と明らかに違う点は、まったく性質の異なるものを同質のものとみなして定義することが出来る点、かなあと思う。

f:id:zecl:20150927173514j:plain

これを難しいと言われると、もうどうしようもない。

f:id:zecl:20150927173835j:plain
カーリーブレイス。まあつまり中括弧のことなわけですが。洞窟物語のキャラにカーリーブレイスって居たなと。

スコープがインデントで区切られるという構造のオフサイドルール(軽量構文)。Pythonインスパイア。
これに対して、インデントにしばられない冗語構文もあるが、(どうとでもなるので)そっちは別段覚えなくてもよいと思う(個人の感想)

オフサイドルールは最初こそ少しとっつきにくいかもしれないが、ルールがシンプルなのですぐに慣れることができるだろうし、実際明確でわかりやすいように思う。ただ、人によっては「見た目キモすぎぃぃぃ」と言う反応をする方もいる。それも理解できないこともない。慣れないものを見ると拒絶反応を示すというのはごくごく当たり前の反応だし。無理なものは無理だし。生理的に受け付けないので付き合えません!(No Thank you!)とかもまあ現実世界でも実際あるわけだし、世知辛いですね(なんの話)

f:id:zecl:20150927174252j:plain
唐突には挿入した息抜き用の画像。 ぐらばく(@Grabacr07) さんが作ってくれたものです。頼んでもいないのに。(写真の人物本人の使用許可あり) かなりの謎コラだけど、非常によい表情です。

f:id:zecl:20150927212744j:plain

f:id:zecl:20150927174834j:plain
変数は、値にわかりやすい名前を付けて定義しておくことで、続くコードで再利用するためのもの。
let で変数を作り出すことを変数束縛と言います(くどい)

f:id:zecl:20150927175027j:plain
例えば破壊的代入(再代入)を許すようなプログラムでは、変数が異なる状態を持ち得るのでその変数を参照する場合「この変数の今の値は何だろう?」と観測しなければならなくなり、複雑性を生み出す要因となる。つまり心配ごとが増える。

f:id:zecl:20150927175436j:plain
let による変数束縛ではデフォルトで破壊的代入(再代入)ができません。が、mutable宣言をすることで、破壊的代入が可能な変数として宣言することもできます。しかし、関数プログラミングというスタイルでは極力「破壊的な状態の変化」というものを避ける傾向にあるので、破壊的代入をデフォルトで許さないようにしている。これはスタイルとしての「副作用をできるだけ使わない」ということを推奨/支援しているとも言える。

f:id:zecl:20150927175507j:plain
例えば変数が状態を持ってしまうと、その変数を参照する箇所で「この変数の今の値は何だろう?」と注意しなければならない。

f:id:zecl:20150928092712j:plain
この例においては、2番目の出力は「5963」ではなく「4649」と表示される。

F#は、変数を後から宣言した同名の変数でシャドウイング(隠ぺい)することができる。シャドウィングが使えることで、破壊的代入をしなくても不便さはそれほどなくなる。また、プログラムのスコープが明確になり、書きやすいだけではなく非常に読みやすいプログラムとなる。

f:id:zecl:20150928092752j:plain
immutableな値として、同名の変数として宣言するシャドウィングを用いる場合、 スコープの中の直前のものにのみ着目すればよいので、余計な複雑さを排除できてコードの見通しがくなり、プログラミングそのものに集中できる。 ※同一モジュール上のlet 宣言をシャドウィングすることはできないという制限あり

f:id:zecl:20150927180036j:plain
プログラマーのかわりに関数を利用しているコードの型などから、コンパイラがよしなに型をつけてくれる型推論。関数の引数や戻り値にも型を書かなくてよいし、可能であればジェネリックな関数として自動的に推論してくれたりする。 変数の定義も関数の定義も、letというキーワードで統一的に定義できるようになっていてうれしい。

少し乱暴だが、F#では let キーワードを覚えるだけで、かなりのプログラミングが書ける。match式あるいは if 式を覚えれば条件分岐もできる。 くわえて 再帰のための let rec を覚えれば、ループ処理が書けるので、いとも簡単に三種の神器を手に入れることができる(アッハイ)

f:id:zecl:20150927181717j:plain

f:id:zecl:20150927181745j:plain
JavaScriptのコードでカリー化関数と部分適用の説明。 突然のJavaScript!なのだが、社内勉強会用に作った資料の流用だったりするので。

f:id:zecl:20150927181853j:plain
f:id:zecl:20150927182004j:plain
F# ではカジュアルに無意識にカリー化関数が書けるので、うれしいという話

f:id:zecl:20150927182101j:plain
高階関数のうれしいところ

f:id:zecl:20150927182147j:plain
高階関数を扱いやすくするのに必要な機能とかの話。 F#では関数内関数が簡単にかけてうれしい。

f:id:zecl:20150927182253j:plain
高階関数を書きやすくする要因のひとつとして、関数内関数が無理なくかけるかというのがある。

C# でも書けないこともないが、どうしても書きにくいので書くモチベーションにならない。 すすんで書くようなものではないことは多くの人が知っているし、C# では素直に名前を付けてメソッドとして切り出して書くべきだ。

F#では、無理なく関数内関数を書くことができるのでなんの抵抗も生まれないし、その場限りの関数であれば、関数内関数として定義することはごくごく当たり前に行われる。これは、インデントによるオフサイドルールとも相性が良く、書き方/スタイルとして非常になじむ。関数プログラミングを支援/推奨している言語では プログラミングスタイルそのものが変わります。

f:id:zecl:20150927183756j:plain
関数の合成のしやすさも、関数プログラミングのしやすさの指標と考えられます。C#で合成関数を書こうとするとかなり骨が折れます。しかもこれは、特定の型に関する合成しかできません。

F#で定義した関数は、コンパイラ型推論と自動ジェネリック化のメカニズムによって、可能であれば暗黙的にジェネリックな関数となります。これにより非常にシンプルな記述で、あらゆる型の関数を合成するような関数(演算子)が表現できてしまうというわけです。こういった特徴を兼ね備えているので、小さな関数を巧みに組み合わせて、より大きな関数を構築していくという関数プログラミングというプログラミングスタイルが違和感なく行えます。

f:id:zecl:20150927184323j:plain
レキシカルスコープのほうが、コードを追いやすい話。 ダイナミックスコープと言えば、Lisp とか?

f:id:zecl:20150927184502j:plain
仮にダイナミックなスコープだとすると、10 が出力される ダイナミックスコープで解決されてうれしい場面ってどの程度あるのだろうか。罠でしかない感。

F#はレキシカルスコープを採用しているので、オフサイドルールやシャドウィングの仕様ともよくマッチしていて人道的でわかりやすいスコープとなっている。なので F# のコードは非常に読みやすい(追いやすい)。これが仮にダイナミックなスコープだとすると、関数を適用したときに、どのような結果をもたらすかコードリーディングだけで判別しにくくなってしまう。高階関数とかこわくて使えない。

f:id:zecl:20150927185203j:plain
高階関数の利用を容易にする条件はこんな感じかな 言い換えると、関数プログラミングしやすい条件ともいえる。


ここから、「続きはWebで」のぶぶん
f:id:zecl:20150927185552j:plain
有名なアレ。 VBの方ですか?Nothingもっとこわい?

f:id:zecl:20150927185713j:plain
C#でも null非許容型が欲しいなんて話はたびたび聞きます。 テストでカバーすればなんとかなる? そういう話ではないんです。

f:id:zecl:20150927185818j:plain
F# では通常、null 値を値にも変数にも使用しません。デフォルトで危険性を回避するように設計されています。例外的な場合を除いて、nullの使用はできる限り避けるよう言語設計されています。

f:id:zecl:20150927190029j:plain
通常F#で定義されているクラスには null は代入できません。 ムリヤリつっこうもとしてもコンパイルエラーとなります。

f:id:zecl:20150927190144j:plain
そうは言っても、.NET Frameworkの上にある以上 F# でも null 値を扱うことはあります。 ただし、その状況はとても限られていて

  • .NET API を呼び出して引数として null を渡す場合
  • .NET のメソッド呼び出しからの戻り値または出力パラメーターを解釈する場合

のいずれかしかありません。これらの状況に対しては AllowNullLiteral 属性を使用して対応します。
これを使用することで null 許容する型を明示的に定義することができます。

この例ではその2つの状況いずれにも当てはまらないので、 AllowNullLiteral は使用すべきではありません。
また、null チェックは強制することができないので、チェック漏れがあれば当然 NullReferenceExceptionとなります。

ちなみに、F#で定義されていてF#からのみ使用する型の場合、null 値を作成する方法は、 Unchecked.defaultof または Array.zeroCreate のいずれかの関数を使用した場合に限られます。



f:id:zecl:20150927190535j:plain
とはいえ、値がない状態というのをプログラミングで表現したいシーンはたくさんあります。そんなときに役立つのがオプション型で、これにより「値がないかもしれない」ということを型として明示することができます。計算不能や計算失敗などにより、実際の値が存在しない場合などに使えます。null の厄介な点は null であることが型の情報として現れないことで。nullは参照型であればどんな型の値にもなり得るので、 null参照によるエラーというのは実行してみるまで誰にも分からないからです。つまり、nullが来るかどうかの判定をプログラマーが正確に管理しなければならないこと意味します。これは実際難しいことです。

f:id:zecl:20150927190812j:plain
オプション型を導入することで、 null値を使用することなく安全に「値がない状態」を表すことができます。

f:id:zecl:20150927190935j:plain
関数プログラミングとは直接は関係ないところですが、F# のとても面白い(エキサイティングな)機能なのでご紹介します。

f:id:zecl:20150927191038j:plain
「インフォメーションリッチプログラミングから LINQ を引いて残った物が 型プロバイダーだよ。」「わけがわからないよ。」

TypeProviderのドキュメントによると、「基本的に定型のデータあるいはサービス情報空間をF#プログラミング内に導入することを目的としています 」とのこと。コンパイル時に型のない情報に型を与えることで、F# コード上であらゆる情報を安全に扱えるようにする仕組み。

f:id:zecl:20150927191108j:plain
型プロバイダーの主目的はコンパイル時プログラミングではないものの、型のないものを扱う際に生じやすいしょーもないバグを未然に防ぐことができるソリューションと解釈することもできます。型のないものを安全に扱える世界があなたの手中にといったところです。

f:id:zecl:20150927192212j:plain
ごむごむ

f:id:zecl:20150927214004j:plain
いろいろできそうです(オラワクワクしてきたぞ)

でも、TypeProviderはDSLなどをメタプログラミングするためのものではないと、TypeProviderのドキュメントに書かれています。
ですがが、まぁ無視してしまって全然よい気はしますね(実際にさまざまなTypeProviderでかなり無視されている) 特に学習目的であればメタプログラミング目的の方がとっつきやすいかもしれませんので。



f:id:zecl:20150927192302j:plain
NuGetからTypeProviders.StarterPack を導入するのがもっとも近道。 TypeProviderを作る際に必要な便利部品がひととおり用意されている。

f:id:zecl:20150927192351j:plain
スキーマが動的に変化することは想定されていないので、スキーマがコーディング中やプログラムの実行中に安定しているかということは、もちろん前提として必要。

ここで言う .NET の型システムへのマッピングとは、つまりクラス だったり 構造体だったりに対するマッピング。F# の型システムと言っているのは、主に判別共用体やレコード型に対するマッピングを意味している。関数プログラミングとの親和性を考えて、F# の型システムの型を生成するのか、あるいはオブジェクト指向プログラミングになじみやすいような型を生成するのか、そのあたりはよく検討してデザインする必要がある。

関数プログラミングとの親和性をとる場合の例でいうと、判別共用体は再帰的な構造を表現するのにとても適正しているので、冒頭で紹介した弾幕記述言語の F# 実装であるところの FsBulletMlでは実際に、XML(外部DSL)から判別共用体(型付き内部DSL)への安全なマッピングのためのTypeProvider提供しています。

f:id:zecl:20150927193158j:plain
セッションでは、いちおう簡単なDemoだけ紹介しました。



f:id:zecl:20150927194029p:plain

f:id:zecl:20150927194037p:plain
コンパイル時にメタデータからリアルタイムで型がつくられる。Visual Studioとも連携しているので、インテリセンスも効くという感じ。
こんな簡単なDemoからでも、「TypeProviderでなんか面白いことができそうだな!」ということが容易に想像できると思う。


f:id:zecl:20150927194247j:plain
今回のDemoのような単純なものであれば少し勉強すれば、すぐに作れるようになりますが、ある程度複雑なものになると、ぐんと難しくなります。
ただ、それは関数プログラミング的な側面で難しいという意味ではなく、メタプログラミングの側面として難しいという意味です。

TypeProviderの作り方について学ぶには、日本語情報に限らず海外の情報もまだまだ少ないので、 FSharp.Data等のライブラリのソースを読んで勉強する感じになるかと思います。 github.com

f:id:zecl:20150927194634j:plain

f:id:zecl:20150927194644j:plain
F#に限らずさまざまな言語の話も飛び交う感じの割とフリーダムなゆるい集まり。
私も何度かお邪魔して楽しくすごさせていただいています。ほとんどF#を書いたことがないというような人でも気軽に参加していただける勉強会です。
F#に詳しい参加者も多いので、やさしいF#erにマンツーマンでわからないことをいろいろ教えてもらえる(かも!?)

f:id:zecl:20150927220102j:plain
日本全国のF#er の集まり(online)という位置づけ(のはず)

gitterで F# に関する情報交換などがなされています gitter.im



f:id:zecl:20150927194719j:plain
英語ですが、F#に関するさまざまな情報がまとまっています(無料会員登録可) 特に言語の発展に物申したい場合は有料会員になる感じだと思います。 F# Software Foundation

@yukitos さんは、こちらのボードメンバーでもあります。

f:id:zecl:20150927195313j:plain
f:id:zecl:20150927195323j:plain
他のプログラミング言語に比べてF#が特別難しいというようなことはありません(もちろん難しい部分も当然あります)。

F#は高階関数が扱いやすく関数プログラミングがしやすい言語です。なので、プログラミングのスタイルは自然と関数プログラミングに軸をおいたものとなります。


よく、F#って何に使えるんですか?等の質問をいただきますが、VBC#とほとんど同じ場面で使えると同じように、ほとんどの場面で同じようにF#も使えます。F#を選択する理由があるとするならば、関数プログラミングのスタイルおよびそれによって享受できるメリットを重視するかしないかくらいのものです。それ以上でもそれ以下でもありません。関数プログラミングのスタイルを重視しないのであれば、選ぶ理由はないでしょう。 現状、世間一般ではそれを重視しないのが多数派であり、人材の確保が難しかったりある程度学習コストが高いという側面があるので、選択肢としてあがることも少なく(んなもんで人材が増えないという悪循環)、結果「流行っていない」ということになります。

手続き寄りの一般的なプログラミングを習得している人に対して、抽象化の道具として新たに関数プログラミングを軸にすることを提案することは 実際間違いではないし、どちらかというと筋がよいことだと思うんだけど、それを必要としている人もいるししてない人もいるというのは理解できます。で、実際いまのところは(残念ながら)必要とされていない(らしい)ので、F#流行っていないということになります。大変良い言語(実際)ですが、今のところMSが推す気配なし!ということもあって、だいぶ闇は深いです。

f:id:zecl:20150927200510j:plain

長々と(ポエム)書きました(しゃべりました)が、少しでも興味を持ってもらえたなら、うれしいな #うれしいな

F# Build Tools for Unity(ゲームのやつ) - UniFSharpのご紹介

F# C# Unity エディタ拡張 ユニティちゃん 誰得

これは F# Advent Calendar 2014の延長戦、 30 日目の記事です。

f:id:zecl:20141229010705j:plain

書いたきっかけ

結局、25日に間に合いませんで。ゆるふわ #FsAdvent に急遽参加しました。そんなわけで、ML Advent Calendar 2014も合わせてどうぞ。 この記事は非常に誰得でニッチな内容を扱います。ほとんどの場合役には立たないでしょう。F# らしい成分もあまりありませんので、まあ適当に流してください。


UniFSharpとは?

UniFSharpは、私が無職だった(ニートしていた)ときに作成した Unityエディタ拡張 Assetです。割と簡単に導入することができます

f:id:zecl:20141229020738p:plain

Unityでのゲーム開発はMacで開発する方がなんとなく多い印象がありますが、わたしがVisual Studio使いたい勢ということもあり、こちらWindows専用となっています。Mac対応はそのうちするかも(?) というか、リポジトリ公開してますから、誰か適当にうまいことやっちゃってください。


上の動画の内容は少し古いですが、概要は伝わるかと。UniFSharpを使うと、Unityエディタ上のAssets/Create/F# Script/NewBehaviourScript のメニューから選択するだけで、F# Scriptを作成できます。 そして、Unityエディタ上でF# ScriptをDLLにビルドすることができます(MSBuild利用)。 Visual Studio(IDE)とも連携するよう実装しており、これにより F#(関数型プログラミング言語)でUnityのゲーム開発がしやすくなります。いわゆる、"作ろうと思えば作れるの知ってるけど、面倒くさくて誰もやらなかったことをやってみた系のツール"です。まぁ、実際やるといろいろ大変。また、オープンソースヒロインのユニティちゃん(ユニティ・テクノロジーズ・ジャパン提供)をマスコットキャラクターに採用しました。ユニティちゃんの音声で時報やイベント、ビルド結果の通知を受けられるという、本来の目的とはまったく関係のない機能も提供しています。


Unityを使うならまぁ当然 C# 一択です。がまぁ、趣味で使う分にはフリーダム。


1000 人 Unity ユーザがいるとすると、その中の 0.01 人は F# ユーザーかもね(適当)


ちなみに、UniFSharp自体も F# で書かれています(ちょっとC# Scriptが混ざってます)。そう、基本的には F#で Unity のほとんどの部分(エディタだろうがゲームだろうが)を書くことができます。この記事では、UniFSharpが提供する機能および、それがどのように実装されているのかについて書きます。ここで紹介でもしないと、GitHubリポジトリを誰も覗いてくれることもないでしょうし。ハイ。

ご利用の際は、まぁいろいろあると思います(お察し)。覚悟しましょう。

この記事を読むその前に...むろほしりょうたさんの初心者がF#をUnityで使ってみた!という記事をオススメします。


F# Scriptの作成

Unityのエディタ拡張では、カスタムメニューを簡単に作ることができます。

f:id:zecl:20141224002558p:plain

f:id:zecl:20141224002627p:plain


F#で実装する場合、Moduleに定義した関数にMenuItem属性を付けるとよいでしょう。


UnityのEditorWindowは、ScriptableObjectインスタンス化されたものです。ShowUtilityメソッドを実行すると、必ず手前に表示し続け、タブとして扱えないウィンドウを作れます。C# で作る場合と基本的に同じです。難しくないですね。以下のウィンドウでは、選択されたテンプレートファイルを元に、F# Scriptを生成するという機能を提供しています。

f:id:zecl:20141224002702p:plain



F# Scriptのテンプレートの例


テンプレートファイルを元に F# Script ファイルを生成したら、その生成したファイルを Unity エディタに Asset として認識させる必要があります。認識をさせないと、Unity の Projectウィンドウ上に表示されません。Assetとして登録する場合、F# Scriptファイルの名前の編集が確定したタイミングで行うようにします。EndNameEditActionクラスを継承し、Actionメソッドをオーバーライドして実装します。AssetDatabase.LoadAssetAtPathで、F# ScriptをUnityEngine.Objectとして読み込み、ProjectWindowUtil.ShowCreatedAssetで、Projetウィンドウ上に表示させることができます。

f:id:zecl:20141224002941p:plain



ちなみに、Visual F# Power Tools(VFPT)では、フォルダ名はプロジェクト全体で一意である必要があるので、UnityのProjectウィンドウ上で階層をフリーダムに作られると厄介なので、そのあたりの階層構造も一応 チェックしていたりという感じです。変な階層を作られると、.fsprojファイルがぶっ壊れて開けなくなっちゃいますからね。




Inspectorで F# コードのプレビューを表示

Inspectorウィンドウで F#コードのプレビューを表示するためには、カスタムエディタを作成します。ただし、カスタムエディタはDLLのみでは実装を完結することができないため(謎の制約)、C# Scriptで。 http://forum.unity3d.com/threads/editor-script-dll-and-regular-script-dll-not-adding-custominspector-scripts.107720/

f:id:zecl:20141224002941p:plain


Editorを継承しOnInspectorGUIをオーバーライドし、Projectウィンドウで選択されたF# Scriptを読み込んで表示するよう実装します。雑ですが以上。

F# DLL のビルド

Unity上から MSBuildでビルドするだけの簡単なお仕事です。これといって特筆すべきことはありません。誰かソースきれいにして。


UniFSharpでは、ビルドの結果をユニティちゃんが通知してくれます。ビルドエラーだとこんな感じ

f:id:zecl:20141224003402p:plain


F# Scriptのドラック&ドロップについて

「UniFSharp を使えば F# Script ファイルをUnity上で作れる」とは言っても、実際にScriptファイルとして動作するようには実装していないくて、実際はDLL化したアセンブリをUnityで読み込んで利用しているため、通常は Projectウィンドウに表示しているだけの F# Scriptファイルを、Inspectorウィンドウにドラック&ドロップしGameObjectにComponentとして追加することはできません。UniFSharpでは、アセンブリの内容を解析して、疑似的に F# Scriptファイルをドラッグ&ドロップしているかのような操作感覚を実現しています。

F# DLLとF# ScriptからMonoBehaviourの派生クラスを探索するモードは2種類用意していて、1つは、F# Scriptファイルを読み取って、シンプルな正規表現でクラス名を抽出し、アセンブリからMonoBehaviourの派生クラスを検索する方法。もう一つは、F# ScriptファイルをF# コンパイラサービスを利用して、解析して厳密にクラス名を抽出する方法。前者は精度は低いが早い。後者は精度は高いが遅い。それぞれ一長一短がある。

カスタムエディタということで、F# コンパイラサービスを利用する部分を除いては、またC#


F# コンパイラサービスを使って、F# Scriptファイルから名前空間を含むクラス名の探索はこんな感じ。



ところで、F# コンパイラサービスの対象フレームワーク.NET Framework4以上です。いまはまだ Unityでこのアセンブリを読み込むことはできません。残念!!なのですが、ここで、Microsoftが提供しているILMergeという神ツールを使う(苦肉の策)ことにより、それを回避し実現してる(アッハイ)。

F# Projectファイルの操作とVisual Studioとの連携

Unityエディタで F# Script を作成することをサポートしたということは、つまり、IDEとの連携もサポートするってことだよね。Projectウィンドウ上でF# Scriptファイルを追加したり、ファイルのパスを移動したり、ファイルを削除したタイミングで.fsprojファイル(XML) の内容が書き換わってくれないと、それぜーんぜん役に立たない。そういうこと。この実装がけっこー面倒くさかった...。

こんな感じ


Assetを追加・削除・移動した際に独自の処理をしたい場合は、AssetPostprocessorを継承して適宜処理を実装する。さらに、それがUnityで標準では扱われないファイルの場合(まさに今回の F# Scriptがこの場合)には、OnPostprocessAllAssetsメソッドを実装する。そこで .fsprojファイルをごにょごにょすることで、これを実現できる。

コードは、こんな雰囲気(あばばばば)


また、UnityのProjectウィンドウ上でF# Scriptをダブルクリックした際に、Visual Studio上でそのファイルをアクティブにする動作を実現するために、EnvDTEを利用した。 http://msdn.microsoft.com/ja-jp/library/envdte.dte.aspx


これはきな臭い...。UniFSharpがWindows専用であることが滲み出ているコードですね。はい(真顔)




あと、Retryビルダー。アッハイ。モナドじゃねえっス。


UniFSharpのオプション画面

ユニティちゃんの背景が印象的な画面です。

f:id:zecl:20141224003556p:plain


このオプション画面で、作成するF# プロジェクトの構成の詳細を設定できます。細かい説明は省きます(雑。

ユニティちゃんの機能もろもろ

ユニティ・テクノロジーズ・ジャパンが無償で提供してくれているユニティちゃんのAsset に同封されている多彩な音声。せっかくあるので使ってみたい。特に「進捗どうですか?」とか使わない手はない。そういや、いわるゆる萌え系だとか痛い系のIDEって結構あるけど、しゃべる感じのやつってあんまりないよなぁ。とかいうのが一応実装動機ということで。

f:id:zecl:20141224003648p:plain


  • ・起動時ボイス(ON/OFF)
  • ・ビルド時ボイス(ON/OFF)
  • ・進捗どうですか?(ON/OFF, 通知間隔指定あり)
  • ・時報通知のボイス(ON/OFF)
  • ・イベント通知のボイス(ON/OFF)
  • ・誕生日のお祝い(ON/OFF, 日付を指定)

f:id:zecl:20141224003918p:plain


F#でUnityゲーム開発する気はなくても、Unityをお使いの方で、ユニティちゃんに「進捗どうですか?」とか言われたい人は、まぁ使ってみてくださいという感じで(適当)。

Unityエディタ上で、音声ファイルを再生したい系の人は上記のような感じのをひとつこさえておけば、ハカドルかもね。

f:id:zecl:20141224003821p:plain


MonoDevelop、Xamarin、Visual Studioで Unity の F# DLLデバッグ

UniFSharpとは直接は関係ありませんが、Unity で F# DLLをデバッグする方法も紹介しておきたい。

基本的には、Unity ユーザーマニュアルに書いてあるとおりにすればよいです。 Unity プロジェクトでの Mono DLL 使用 / Using Mono DLLs in a Unity Project

ということで、.fsproj のビルド後イベントに、下記のような感じで設定しておくと捗るかもしれません(パスとかは適当に変えて)。


Visual Studio 2013 Tools for Unityが無償提供され、あらゆるアプリを開発できる最強の開発ツールとの触れ込みのVisual Studio 2013 Community Editionが無償提供されたことで、誰でもVisual Studio で Unityのデバッグ実行ができるようになりました。本当にいい世の中になったものです。F# をお使いなら、 Visual F# Power Toolsも利用できますし、めしうま状態必至。



おまけ

Unityえふしゃーぷまん達


意外といらっしゃる。もちろんこれですべてではない。

Unity は良くできているゲームエンジンなので、F# でも使いたい!という気持ちはわかりますが、一般的には、F# でゲームを作りたいなら MonoGame あたりを選択する方がかしこいんじゃないでしょうか。はい。とは言え、 身の回りにUnity F# マンがいたら、ぜひとも情報交換などしてみたいですね。

ところで、わたくしごとで恐縮ですが、8か月以上という長いニート期間を終え、 12/1 から株式会社グラニで働いております。みなさんご存じ「最先端のC#技術を使った」ゲーム開発をしている会社です。とてもよい環境で仕事をさせていただいています。ということでわたくし現在東京におりますので、F# 談話室がある際にはぜひ遊びに行きたいです。趣味のF#erからは以上です。

型プロバイダー(TypeProvider)のちょっとしたアレコレ

F# F#3.0 型プロバイダー TypeProvider BulletML DSL

一応、型プロバイダー(TypeProvider)のとりとめもない話 の続き。 ちょっと草植えときますね型言語Grass型プロバイダーを作った後、少し思い違いをしていた事に気付いたのが事の発端。この記事では、FsBulletML.TypeProvidersを作成する過程で得た、型プロバイダーについてのちょっとしたアレこれについて書いてみる。

オレは型プロバイダーに対する思い違いをしていた(雑魚)

  FsBulletMLという弾幕記述言語ライブラリを作っています。このライブラリでは、弾幕記述言語BulletML(XML形式等)を読み込んで弾幕を表現する判別共用体(内部DSL)の型を生成するということをやっています。構想の段階で BulletML(XML形式等) に判別共用体(内部DSL)の型を付ける型プロバイダーの提供も考えていたが、型プロバイダーでは判別共用体を作ることができないという理由から、このライブラリでの型プロバイダーの提供は一時保留としていた。


  で、
 

 


  大きな思い違いをしていたと気が付いた。型プロバイダーによってメタデータを元に"判別共用体の型そのものは作ることはできない"が、"型プロバイダーで作成した型が持つメソッドやプロパティを通じて、メタデータを元に作成した判別共用体を返すことはできる"ということに(気付くの遅すぎ)。
   

FsBulletMLで型プロバイダーを提供してみよう

ということで、FsBulletMLで型プロバイダーを提供してみることにした。

FsBulletMLで提供している3つの外部DSL(XML形式、SXML形式、FSB形式)をBulletml判別共用体(内部DSL)に型付けしてくれる型プロバイダーを提供したい。FsBulletMLでは、もともとFsBulletML.CoreFsBulletML.Parserという2つのパッケージをNuGetで提供している。これに加えて、FsBulletML.TypeProvidersというパッケージを新たに作成して提供したい。


FsBulletML.TypeProvidersを作成する過程で学んだことについて書いてみる。以下で現れる TypeProviderForNamespaces クラスは、FSharp.TypeProviders.StarterPack を利用している。


 

型プロバイダーに渡すことができる静的引数の種類

静的引数を扱う型プロバイダーについて、メタデータとしてもっとも渡されることの多い静的引数は string(文字列型) だろう。実際、文字列さえ渡すことができればだいたいなんでもできる。ただ、他にどのような型を渡すことができるのかについても把握しておきたい。


基本的には、プリミティブ型 (F#)を静的引数として渡すことができる。ただし、nativeint, unativeint, System.Void, unit など CLI定数リテラルとしてコンパイルできないものは型プロバイダーの静的引数として渡すことができない。また、enum(列挙型) も基になる型は (sbyte、byte、int16、uint16、int32、uint32、int64、uint64、char) のいずれかであるため、静的引数として渡すことができる。


 


  実行結果

 

 

へぇ。とくに面白くはない。


型プロバイダーの実行部分は部分的な制限がある

型プロバイダーでは、メソッドやプロパティの実装をコードクォートによって行うため部分的な制限がある。

たとえば、

Valueプロパティの値を、XMLドキュメントでも参照できるようにしている。上記のコードは、コンパイルが通るので一見良さそうにみえるが、コードクォート内から value に束縛した値をうまく参照することができないため、以下のようなエラーとなる

f:id:zecl:20140825175254p:plain


なんてことはない。直接コードクォート内に記述するとうまくいく。

ただこれだとコードが重複してしまって気持ちが悪い。


ひとつは、以下のようにFSharp.PowerPackを用いてコードの重複を避けるという方法も考えられるが、Linq.QuotationEvaluationは万能とはいいがたいし、 FSharp.PowerPackに依存するのも何か違う感じがするのでこれは避けたい。

 

通常は、module に関数を外出しすることでこれを回避する。

  これはコンパイルも通るし Sample4 型プロバイダーを利用する側のコードもコンパイルが通るので良さそうに見える。  

f:id:zecl:20140825180549p:plain


  しかし、実行すると次の例外が発生する。当然と言えば当然だが型プロバイダーの実行時に public ではない module を参照することはできないからである。  

f:id:zecl:20140825180421p:plain

 

かといって、 Hogehoge module を単に public にするだけだと、見せたくないものがそのまま垂れ流しで見えてしまうので、どうも具合がわるい。そこで、CompilerMessage属性を利用するという苦肉の策を使う。

 
ところで、EditorBrowsable氏~ 人気ないの~?ないの~?
F# IntelliSense doesn't respect the EditorBrowsable attribute


他のDLLに依存する型プロバイダーを作る

FsBulletMLで型プロバイダーを提供するにあたって、実装はFsBulletML.CoreおよびFsBulletML.Parserに依存するようにしたい。 FsBulletML.Coreは、XML形式のBulletMLをパースする機能を持っている。FsBulletML.Parserは、SXML形式とFSB形式をパースする機能を持っている。 それぞれのDLLを参照してしまえば、ちょっと草植えておきますね型言語Grass型プロバイダーと同じようなノリで簡単に実装できるはずだ。そう考えた。 実際、実装そのものは容易にできた。しかし実行するとうまくいかない。単に依存対象のDLLを参照しただけではだめなのだ。型プロバイダーのコンパイルは通るが、利用時にエラーとなる。型プロバイダーのコンパイル時に参照できている DLL が実行時には参照できないことが原因だ。


  たとえば、次のコードの DLL を参照した型プロバイダーを作る。


型プロバイダー

コンパイルが通るし一見良さそうに見えるが、この型プロバイダーを利用しようとすると以下のようになる。

f:id:zecl:20140825181323p:plain

型プロバイダーのコンパイル時に参照できている DLL が型プロバイダーの実行時に参照できていないためにこのようなエラーとなる。 Library1.dll が存在するパスを、型プロバイダーの探索対象にあらかじめ登録しておく必要がある。 TypeProviderForNamespaces クラスのRegisterProbingFolderメソッドでこれを解決できる。

このように実装することで、型プロバイダーと同じパスに存在するDLLが実行時に参照可能となる。


他のNuGetパッケージに依存した型プロバイダーを作ってNuGetで配布するときのやり方

FsBulletML.TypeProvidersは、FsBulletML.CoreおよびFsBulletML.ParserのNuGetパッケージに依存するかたちで配布したい。この場合、他のNuGetパッケージが展開されるフォルダのパスを考慮した実装が必要となる。もっとも良さそうな方法は、package.configのXMLを読み込んで参照するパスを解決する方法が考えられる。 FsBulletML.TypeProvidersでは、下記のような感じで、型プロバイダーが依存するNuGetパッケージのパスを探索するよう実装することでこれを解決した。

ということで、FsBulletML.TypeProvidersリリースしました。

f:id:zecl:20140825181636p:plain


型プロバイダーが参照するファイルの更新チェックを実装する

型プロバイダーが想定するスキーマが変更された場合、F# 言語サービスがそのプロバイダーを無効化するようにシグナルを通知することができる。シグナルが通知されると、型プロバイダーがVisual Studio上でホストされている場合に限り、再度型チェックが行われる。 これを利用して型プロバイダーが参照するファイルの更新チェックを実装することができる。具体的には、FileSystemWatcherクラス等でファイルの状態を監視し、適切なタイミングで CompilerServices.ITypeProvider インターフェイス (F#)のInvalidateメソッドを呼び出すように実装すればよい。FsBulletML.TypeProvidersでも実装しています。


ソースはここにあります。 FsBulletML/src/FsBulletML.TypeProviders at master · zecl/FsBulletML · GitHub


消去型と生成型

最後に、@bleisさんのTypeProviderについて、勝手に補足で紹介されていた生成型の型プロバイダーのひじょーにシンプルな例についてご紹介。


まず、消去型のサンプル


型が消えてますね。

f:id:zecl:20140825181814p:plain


f:id:zecl:20140825190217p:plain


ILDASMで逆コンパイルした結果も見てみましょう。 はい。型が消去されています。


これを生成型の型プロバイダーに書き直してみます。

ポイントは、ProvidedTypeDefinitionIsErased = falseとすること。 ProvidedAssemblyで一時アセンブリを作り、そのアセンブリAddTypesで生成する型を登録することです。 この例では、アセンブリ内に定義したPiyoクラスを継承する型を生成しています。

f:id:zecl:20140825181847p:plain


f:id:zecl:20140825181909p:plain


ILDASMで逆コンパイルした結果を見てみましょう。


Visual Studio上でも確認ができたように、型が消去されずに、Piyoクラスを継承したHogeAクラスが型プロバイダーによって生成されていることが確認できます。




うほ!とても面白いアイデア。 生成型の型プロバイダーだと確かにそういった構想の面白いブツが作れそうですね(wktk)。

ということで、型プロバイダー(Type Provider)のちょっとしたアレコレを書いてみました。それはそうと、Visual F# Power Toolsなのか他の拡張機能なのかわからないけど、型プロバイダーを書いたりデバッグしていると、割と頻繁になにかしらの拡張機能のエラーのダイアログがでてきてウザいですよ(激おこ)。

型プロバイダー(Type Provider)のとりとめもない話

F# F#3.0 型プロバイダー TypeProvider

だらだらと型プロバイダー(Type Provider)のとりとめもない話。ほとんど内容はないよう。 

   

型プロバイダーって何?

F#3.0 から利用できる目玉機能のひとつ。 これが発表されたとき、「LINQ + Type Providers = Information Rich Programming」なんて言われていました。 「インフォメーションリッチプログラミングから LINQ を引いて残った物が 型プロバイダーだよ。」「わけがわからないよ。」

  任意のメタデータを与えると、コンパイラコンパイル時に"型のないもの"に"型を与えて"提供してくれる。それが型プロバイダー。これ、Visual Studio や Xamarin(MonoDevelop) 等のIDEとも連携するということでメシウマ状態必至なわけ。  

 

型プロバイダーが型として提供できないようなメタデータが型プロバイダーに与えられた場合、コンパイルエラーとなる(そのように実装している場合に限る)。型プロバイダーの主目的はコンパイル時プログラミングではないものの、型のないものを扱う際に生じやすいSHOUMONAIバグを未然に防ぐことができるソリューションと言うこともできる。型のないものを安全に扱える世界があなたの手中に的な。

     
型プロバイダーさんステキ!抱いて! 

 

   

型プロバイダーの作成って難しそう(実際難しい)

一応それなりに F# を追っかけているマンなので、某サンプルの「Hello World TypeProvider」くらいはチョロっとかじりました。

  チュートリアル : 型プロバイダーの作成 (F#) 
 

でも、特に作りたいオリジナルのネタもなくって(あるいは既に先駆者がいたりで)、なかなか触れる機会に恵まれまれずにコンニチに至ります(言い訳)。そういう人多そう。実装したいネタがどうこうというより、そもそも難しかったり取っつきにくかったりで手が出しずらいという説もある。もし詳細を知りたいなら FSharp.Data等のライブラリのソース嫁がデフォというなかなかに漢らしい感じだし。何か作りながら覚えようにも、予期せぬエラーとそのメッセージに悩まされることも多いし、デバッグ実行で地道に追っても原因がよくわからず途方に暮れるなんてこともあり...結構ハードルは高い。実際上級者向けではある(個人の感想)。 

 

「型プロバイダーってアレでしょ?作るもんじゃなくて、使うもんでしょ?」 

 

難しいことはよくわからないけど、F# のすごい人たちが「"型のないもの"に"型を付けてくれる"」とっても便利でクールなライブラリを作ってくれていて、「オレのような一般 F# ユーザーは利用者としてその恩恵を受けることができればいいよね。」くらいに割り切って押さえておくだけでもよいという代物かもしれない。

 

   

ごもっともすぎるご意見。汎用的で有用な型プロバイダーを自分で実装するような機会は、あまりないような気がする(便利そうなやつはもう既に誰かが作ってくれていることが多いし)。概要だけ把握しておいて、もし実装する機会があったらそのときじっくり調べながら頑張ればいいんじゃないだろうか。必要に応じて。 

 

    
   【朗報】型プロバイダーは華麗にスルーしても上級者になれる(!) 逆に言うと、型プロバイダーがわかったからって上級者にはなれません。


型プロバイダーの作成って面白そう

2014年8月3日に F# Meetup in TokyoおよびそのPart1という素晴らしいイベントがあり、fsugjp がやまだかつてない程の盛り上がりがあったようです。そのイベントで@kos59125さんによるTypeProviderに関する発表があり、更に後日@bleisさんによる、TypeProviderについて、勝手に補足がありました。素晴らしい。私は参加できませんでしたが、F# Meetup in Tokyo #fsugjp - Togetterまとめで雰囲気だけ味わいました。 触発されて何かしら型プロバイダーを作りたくなった。軽い気持ちで...、とりあえず役に立たない系型プロバイダーを作ってみた。  

    @moonmileさんにフィードバックを頂きました。どうもありがとうございます。 

 

 
ゼロから自分で実装することも不可能ではありませんが...基本的にはこの方法しかないといっても過言ではないので、なにかしらのProvidedType.fs(の類)を使いましょう。 役に立つ立たないは置いておいて勉強したい場合はとりあえずなんか作ってみる。そうじゃやない場合は概要だけ抑えて華麗にスルーする。でいいと思います。 



f:id:zecl:20140825173101p:plain



printf系の "%A" 書式指定子における型の表示レイアウトのカスタマイズ

プログラミング F#

判別共用体を文字列として出力する際に、ケース識別子を宣言する型(判別共用体)の名前を含めたフルネームで文字列化したくなったときのお話。

たとえば、以下を実行すると

次の出力結果を得られる。


それを、以下のような感じに出力するようにしたい。というのが今回のお題。


StructuredFormatDisplay属性を使う

Core.StructuredFormatDisplayAttribute クラス (F#) - MSDNライブラリ

この属性は、%A printf 書式設定やその他の 2 次元のテキストベースの表示レイアウトを使用する場合に、型を表示する既定の方法を指定するために使用されます。 このバージョンの F# で有効な値は、PreText {PropertyName} PostText 形式の値のみです。 プロパティ名は、オブジェクトそのものの代わりに評価および表示するプロパティを表します。

とある。これを使えば、型を表示する際のレイアウトを自由にカスタマイズすることができる。なお、"このバージョンの F# で有効な値は"とあるが、F#2.0からF#3.1までは変更はない(F#2.0より前は仕様が異なる)。


StructuredFormatDisplayAttribute。自作ライブラリをせっせとこさえていたり、某有名F# ライブラリのソースコード等を読んでたり、コンパイラのソースを見ていたりする人なら見覚えがあるかもしれないが、"%A"書式指定子の表示レイアウトをカスタマイズしたい場面はそんなに多くはないだろうし、そこそこマニアックな(割とどーでもいい)話題かもしれない。こちら、「実践F#」に載っていないというか、「Expert F#3.0」にも載ってなかったと思うし、いまのところ最新の言語仕様書であるところの「The F# 3.0 Language Specification」にも記載されていないようなので、言語仕様書熟読勢も把握できていない可能性がある。でも、「プログラミングF#」にはサラりと載っていたりする(!)。


単純な例

StructuredFormatDisplay属性を使った単純な例は以下のようになる(ここでは例として判別共用体を対象としているが、その限りではない)。

出力結果は以下のようになる。

使い方めちゃ簡単。

FizzBuzzしてみる

意味もなくFizzBuzzしてみます。

出力結果


この結果から、StructuredFormatDisplay属性を使って型の表示方法をカスタマイズしても、"%s"および"%O"書式指定子に影響を及ぼしていないことが確認できる。"%s"および"%O"書式指定子を指定した場合、いずれも結果的に対象オブジェクトについて Object.ToString仮想メソッドが呼び出されるかたちになるので、判別共用体の場合は既定では上記のように型名が出力される。 override this.ToString () = sprintf "%A" this.Displayというように、ToStringメソッドをオーバーライドする実装を追加すれば、いずれも "%A"書式指定子を指定した場合と同じ結果が得られるようになる。F#2.0より前のバージョンでは ToStringを経由して表示する際にStructuredFormatDisplay属性を参照していたようだが、F#2.0以降はToStringメソッドを経由する場合にはこれを参照しないよう仕様が変更された。

StructuredFormatDisplay属性で指定した{PropertyName}を実装していない場合

ちょっと例を変えて、レコード型にしてみる。

出力結果

とまあ、StructuredFormatDisplay属性で指定した{PropertyName}を実装していない場合は、 コンパイルエラーとなるわけでなく例外となるわけでなく、割と残念な感じの出力結果を得ることになる。コンパイルエラーにしてくれてもいいのにー。

判別共用体(discriminated unions)について、型名も含めて文字列化する

さて、本題。

まずは愚直に書いてみよう

StructuredFormatDisplay属性でマークし、表示をカスタマイズする実装を愚直に書き加える。

以下の出力結果が得られる。

おいおい。PreText使おうぜ

あっ。ケース識別子を宣言する型(判別共用体)の名前は固定なので、この場合StructuredFormatDisplay属性のPreTextに集約できるんだったね。

出力結果変わらず。

このTree<'T>判別共用体の場合だけに関して言えば、とりあえずこれで良さそうに見えるし、この方法を取れば他の判別共用体についても都度対応できそうだ。 でも、毎回個別に対応するなんてダルすぎる。汎用的にしたいよねー。

リフレクションで汎用的に実装しよう

Microsoft.FSharp.Reflectionを利用する。

出力結果

ヽ(*´∀`)ノ ワーイ、できたよー

と、思ったけど、待って。違うやん。本当は以下のようなレイアウトで表示したかったんだった(だった!)。


んー、内容的には同じなのでそんなに大きな問題ではないんだけど、若干モヤッとする。 "%A" 書式指定子の表示レイアウトをいい感じに制御するにはどうすればよいのだろう? また、既存の型(例えばOption<'T>型など)の、表示をカスタマイズしたい場合はどうすればよいのだろう?

F#er諸兄、何かご存じであればアドバイス頂きたい。

判別共用体で型付きDSL。弾幕記述言語BulletMLのF#実装、FsBulletML作りました。

プログラミング F# BulletML DSL 弾幕 Advent Calendar MonoGame

この記事はF# Advent Calendar 2013の20日目です。



遅ればせながらThe Last of Usをちびちびとプレイ中。FF14のパッチ2.1が先日リリースされ、メインジョブ弱体化にもめげず引き続き光の戦士としてエオルゼアの平和を守り続けている今日この頃。艦これは日課です。はてなブログに引っ越してきて一発目(ブログデザイン模索中)です。気付けば半年もブログを書いていませんでした(テイタラク)。今年はあまり.NET関係の仕事に携わることができずに悶々としておりましたが、急遽C#の仕事が舞い込んできました。年末はいろいろとバタバタするものです。少しの間ホテル暮らしなのでゲーム(据置機)できなくてつらいです。わたしは大晦日にひとつの節目を迎えます。記憶力、体力、集中力...多くのものを失ったように思います。アラフォーいい響きです(白目)。


上の動画の弾幕は次のBulletml判別共用体で記述されています(MonoGameで動いています)。

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
#r @"bin\Debug\FsBulletML.Core.dll"
open FsBulletML

/// ぐわんげ、二面ボス by 白い弾幕くん
/// [Guwange]_round_2_boss_circle_fire.xml
let ``round_2_boss_circle_fire`` = 
  "ぐわんげ二面ボス by 白い弾幕くん",
  Bulletml
    ({bulletmlXmlns = Some "http://www.asahi-net.or.jp/~cs8k-cyu/bulletml";
      bulletmlType = Some BulletVertical;},
      [BulletmlElm.Fire
        ({fireLabel = Some "circle";},
          Some (Direction (Some {directionType = DirectionType.Sequence;},"$1")),
          Some (Speed (None,"6")),
          Bullet
            ({bulletLabel = None;},None,None,
            [Action
                ({actionLabel = None;},
                [Wait "3";
                  Fire
                    ({fireLabel = None;},
                    Some (Direction (Some {directionType = DirectionType.Absolute;},"$2")),
                    Some (Speed (None,"1.5+$rank")),
                    Bullet ({bulletLabel = None;},None,None,[])); Vanish])]));
      BulletmlElm.Action
        ({actionLabel = Some "fireCircle";},
          [Repeat
            (Times "18",
              Action
                ({actionLabel = None;},
                [FireRef ({fireRefLabel = "circle";},["20"; "$1"])]))]);
      BulletmlElm.Action
        ({actionLabel = Some "top";},
          [Action.ActionRef ({actionRefLabel = "fireCircle";},["180-45+90*$rand"]);
          Wait "10"])])
名前空間 FsBulletML
Multiple items
共用体ケース Bulletml.Bulletml: BulletmlAttrs * BulletmlElm list -> Bulletml

--------------------
type Bulletml =
  | Bulletml of BulletmlAttrs * BulletmlElm list
  | Action of ActionAttrs * Action list
  | ActionRef of ActionRefAttrs * Params
  | Fire of FireAttrs * Direction option * Speed option * BulletElm
  | FireRef of FireRefAttrs * Params
  | Wait of string
  | Vanish
  | ChangeSpeed of Speed * Term
  | ChangeDirection of Direction * Term
  | Accel of Horizontal option * Vertical option * Term
  ...

完全名: FsBulletML.DTD.Bulletml
共用体ケース Option.Some: 'T -> Option<'T>
共用体ケース ShootingDirection.BulletVertical: ShootingDirection
type BulletmlElm =
  | Bullet of BulletAttrs * Direction option * Speed option * ActionElm list
  | Fire of FireAttrs * Direction option * Speed option * BulletElm
  | Action of ActionAttrs * Action list

完全名: FsBulletML.DTD.BulletmlElm
共用体ケース BulletmlElm.Fire: FireAttrs * Direction option * Speed option * BulletElm -> BulletmlElm
Multiple items
共用体ケース Direction.Direction: DirectionAttrs option * string -> Direction

--------------------
type Direction = | Direction of DirectionAttrs option * string

完全名: FsBulletML.DTD.Direction
type DirectionType =
  | Aim
  | Absolute
  | Relative
  | Sequence

完全名: FsBulletML.DTD.DirectionType
共用体ケース DirectionType.Sequence: DirectionType
Multiple items
共用体ケース Speed.Speed: SpeedAttrs option * string -> Speed

--------------------
type Speed = | Speed of SpeedAttrs option * string

完全名: FsBulletML.DTD.Speed
共用体ケース Option.None: Option<'T>
共用体ケース BulletElm.Bullet: BulletAttrs * Direction option * Speed option * ActionElm list -> BulletElm
Multiple items
共用体ケース ActionElm.Action: ActionAttrs * Action list -> ActionElm

--------------------
type Action =
  | ChangeDirection of Direction * Term
  | Accel of Horizontal option * Vertical option * Term
  | Vanish
  | ChangeSpeed of Speed * Term
  | Repeat of Times * ActionElm
  | Wait of string
  | Fire of FireAttrs * Direction option * Speed option * BulletElm
  | FireRef of FireRefAttrs * Params
  | Action of ActionAttrs * Action list
  | ActionRef of ActionRefAttrs * Params

完全名: FsBulletML.DTD.Action
共用体ケース Action.Wait: string -> Action
共用体ケース Action.Fire: FireAttrs * Direction option * Speed option * BulletElm -> Action
共用体ケース DirectionType.Absolute: DirectionType
共用体ケース Action.Vanish: Action
共用体ケース BulletmlElm.Action: ActionAttrs * Action list -> BulletmlElm
共用体ケース Action.Repeat: Times * ActionElm -> Action
Multiple items
共用体ケース Times.Times: string -> Times

--------------------
type Times = | Times of string

完全名: FsBulletML.DTD.Times
共用体ケース Action.FireRef: FireRefAttrs * Params -> Action
共用体ケース Action.ActionRef: ActionRefAttrs * Params -> Action

FsBulletMLリリースしました

弾幕記述言語BulletMLF#実装、FsBulletMLを作りました。Unity 4.3では新たに2Dがサポートされたりで、少なからず需要がないこともないのかもしれず。せっかくなので FsBulletML.Core(内部DSLを提供) および、FsBulletML.Parser(外部DSLを提供) をNuGetに放流してみました(Beta版)。実際に使える(使われる)ライブラリに成長するかどうかはわかりません。

詳しくはこちらをご覧ください

BulletMLとは

BulletMLとは、シューティングゲームにおける弾幕を記述するための言語(外部DSL)で、多くのハイクオリティなシューティングゲームを開発なさっている ABA Games の 長健太氏が作りました。BulletMLが初めて公開されたのは2002年頃でしょうか? もう10年以上前ということになります。シンプルな記述で多彩な弾幕を表現することができ、有限オートマトン的に弾を管理しなくてもよいので楽チン。ということで多くの人から注目を集めました。わたしが存在を知ったのはもう少し後のことですが、当時かなりインパクトを受けて感動したのを覚えています。

本家BulletMLのパーサおよび処理系はJavaで実装されています。RELAX定義DTD定義など弾幕定義自体の簡易的な仕様については公式に公開されているものの、弾幕の処理系の詳細については「ソース嫁」状態という非常に漢気溢れる感じになっています*1。にもかかわらず、多くの人によって様々な言語で移植/実装/改良されていますBulletSML は、BulletMLのS式版(内部DSL)で、ひとつひとつの弾が継続になっているらしいです(あたまおかしい)。最近では、bulletml.js が内部DSLも提供していて、enchant.js用、tmlib.js用のプラグインもあって、ブラウザで動く弾幕ゲーが簡単に作れるようになっているようです。

特定の「何か」を達成するために、最適化された言語を作ることは簡単なことではありません。シンプルで且つ表現力が高くて実用的なドメイン固有言語を作る(デザインする)のはとても難しいことです。BulletMLはとてもよくデザインされていて面白くて魅力的なDSLだと思いました。

DSLについて

以前以下のような記事を書きました。

F#3.0で加速する言語指向プログラミング(LOP)。コンピューテーション式はもはやモナドだけのための構文ではない!!!

きっかけ

今年は例年に比べてIT系勉強会に参加できませんでしたが、夏に「コード書こうぜ!」をスローガンとしたCode2013という合宿イベントに参加しました。その中で、座談会orセミナー形式で複数グループが集まってそれぞれが異なるテーマについて話をする形の「きんぎょばち」というコーナーがありまして、「パーサコンビネータを使った構文解析およびDSLの作成などについて勉強したいです。」というテーマがありました。特定言語に限ったテーマではありませんが、他に「F# や 関数型言語の話題なら少し話せます。」というテーマもあったので、わたしが F# について話をする流れになって、判別共用体の便利さや FParsec を用いた字句解析、構文解析等についてお話してきました。本当は教えてもらいたい側だったのですが...。イベントから帰ってきてから、「何かDSL書きたいなー」と漠然とした思いを持っていました。

BulletMLを 判別共用体で 型付きの内部DSLとして表現できるようにしたらちょっと面白いんじゃあ?」という発想。アイディアとしては3年くらい前から持っていましたが、実装が面倒くさいことになりそうだったので行動に至らずでした。ゲーム開発の世界ではDSLや各ドメインエキスパートのための独自スクリプト言語などを開発/運用することは日常茶飯事で、そう珍しいことではないと風のうわさで聞いたことがあります。わたし自身は、仕事であれ趣味であれ、日頃のソフトウェア開発において本格的なDSLを設計したり実装したりする機会はほとんどありません。非常に興味のある分野/開発手法なので実際に何かを作って勉強してみたい。そう兼ねてから思っていました。良い練習になりそうだし、"評論家先生"というYAIBAに影響を受けたりで、重い腰を上げました(よっこらせ)。

やりたかったこと

弾幕を判別共用体で書きたい(型付き内部DSL)
・従来のXML形式での弾幕を再利用したい(外部DSL)
XML形式は書きにくいしちょっと古臭い。XMLとは異なる形式の外部DSLも利用できるようにしたい。

要は、「内部DSLと複数の外部DSLを両立する。」ということをやってみたい。その辺りを最終的な目標にしました。そもそもC#での実装(BulletML C# - Bandle Games)があるし、何を今さら感があるのも事実ですが、判別共用体による型付き内部DSLを提供するという点で若干のアドバンテージがあります。型付きの内部DSLが使えると何がうれしいって、弾幕を構築する際にF#の式がそのまま使えるということです。つまり、関数を組み合わせて自由に弾幕を組み立てることができるようになります。それってつまり、Bulletsmorphのようなアイディアも実装しやすくなる、と。

XMLのdisり大会。こちら結局どうなったんでしょう。気になります(´・_・`)

DTD定義を判別共用体で表現する

いにしえからの言い伝えによると、「判別共用体は、ドメイン知識をモデル化するための簡潔かつタイプセーフな方法である。」らしいです。 ということで、BulletMLDTD定義を内部DSLとして判別共用体で表現する。ということについて考えてみたい。

以前、こんなやり取りがありました。



言語内に型付きDSLを構築したいようなケースでは、GADT(Generalised Algebraic Datatypes)が欲しくなるようです。つまりこれ、抽象構文木なんかを型付きで表したいときに発生する事案です。しかし、F#にはHaskellGADTsに相当するものはありません。GADT相当の表現自体はOOPスタイルで書けば可能ではありますが、判別共用体で内部DSLを表現したいというコンセプトとはズレてしまうので今回は適用できません。仕方がないので、型を細分化してどんどん型が絞られていくような型を定義します。

BulletMLのDTD定義に沿って、以下のような感じの型を定義すれば、判別共用体による内部DSLの構造(モロ抽象構文木)が表現できます。

長い。ここで定義したBulletml判別共用体は、確かに型付きDSLではあるのですが、BulletMLの仕様に準拠するかたちで型を表現するようにしたのでタイプセーフではないですね。タイプセーフではありませんが、まあそれなりです。今後、より型安全な判別共用体の提供とモナディックな弾幕構築の提供とか、弾幕コンピュテーション式...とかとか妄想しています。あとは、この型を弾幕として解釈することができる処理系を実装すればおkです(それが面倒くさい)。

というか、俺たちのF#にも牙突ください(!)

外部DSLと内部DSLを両立する

f:id:zecl:20131219212500p:plain

内部DSLであるBulletml判別共用体の構造は再帰的な構造になっていなく複雑です。上図のような構成では外部DSLを解析するためのパーサの実装コストが大きくなってしまいます。そこで、より抽象度の高い中間的なASTを用意し、下図のような構成にすることでパーサの実装コストを軽減することを考えます。

f:id:zecl:20131219212527p:plain

中間ASTとは、つまるところXmlNodeの構造そのものなので、以下のように単純な木構造の判別共用体で表現することができる。

  type Attributes = (string * string) list

  type XmlNode =
  | Element of string * Attributes * XmlNode list
  | PCData of string

外部DSLを解析するパーサは、より抽象的で単純な構造にパースするだけでよくなるので、とてもシンプルな実装で済むようになります。実際のパーサの実装例を見てみましょう。

SXML形式のパーサ

かの竹内郁雄氏は、「XMLもぶ厚いカッコのあるLisp」とおっしゃっています。
第1回 Lispの仏さま 竹内郁雄の目力

実際、XML InfosetのS式による具象表現であるところのSXMLがそれを体現していますね。

SXMLの基本的な構成要素はこんな感じです。

[1]              <TOP> ::= ( *TOP* <PI>* <Element> )
[2]          <Element> ::= ( <name> <attributes-list>? <child-of-element>* )
[3]  <attributes-list> ::= ( @ <attribute>* )
[4]        <attribute> ::= ( <name> "value"? )
[5] <child-of-element> ::= <Element> | "character data" | <PI>
[6]               <PI> ::= ( *PI* pi-target "processing instruction content string" )  

不勉強なので、Lispとかよくわかりませんが、要素はlist の car。内容は cdr。属性は @ に続く cdr という感じで表現できれば、BulletMLSXML形式で記述できるようになります。ごくごく簡易的なSXMLのパーサはFParsecを使うと次のような感じに書けます。

このパーサによって、全方位弾の弾幕をこんな感じに記述できるようにます。

スッキリ。ぶ厚いカッコを一掃できて、いい感じですね。

独自形式のパーサについて

「括弧も一掃したいんだが。」という人のためにインデント形式をご用意。インデントで構造化されたデータ表現と言えばYAMLがありますが、YAMLほどの表現力は必要がなくて、弾幕をシンプルに書けさえすればよいので独自形式をでっち上げてみました。

以下のような感じのインデント形式の弾幕も読み込めるパーサも用意してみました。 オフサイドルールな構文をパースしたい場合は、@htid46さんFParsecで遊ぶ - 2つのアンコールの記事がとても参考になりますね。ソースは省略します。

JSON形式のパーサもあってもよいのかも。

Demoプログラムで使った背景画像について

DSL繋がりということで、動画のサンプルプログラムでスクロールさせている背景画像の作成には、DSLで背景画像が作れるF#製のツール「イラスト用背景作成「BgGen」 ver 0.0.0 - ながとのソフト倉庫」を利用させてもらいました。

背景生成に使ったコマンド

rect 0 0 480 680 #f000
circles 1 0 50 #aff

これで宇宙っぽい背景が作れちゃいました。こりゃ便利。

感想

F# の判別共用体で型付きDSLをするのは無理ではないけど、段階的にケースが少なくなってどんどん絞り込まれていくような型の場合、それを解釈する処理の実装コストが大きくて結構つらぽよ感溢れました。あまりおすすめできませんね。欲しいです牙突マジ。

・頭の中ではすでに出来ている(作り方がわかっていると思っている)ことと、実際に作ってみることは、やっぱり結構なギャップがあるね。

・ 結果的に行き当たりばったりのゴリ押し実装になってしまったきらいはあるけど、判別共用体で定義した弾幕が思い通りに動いたときはちょっとした達成感が。おじさんちょっと感動しました。

GitHubに晒したソースコード。ツッコミどころ満載なのはある程度は自覚していますが、より良い方法があればアドバイスを。お気づきの点がありましたら @zecl までお願いします。


疲れた!!!でも、ものづくり楽しいし、F# 楽しい✌('ω'✌ )三✌('ω')✌三( ✌'ω')✌



あわせて読みたい

DSL開発:ドメイン駆動設計に基づくドメイン固有言語開発のための7つの提言 - Johan den Haan
言語内 DSL を考える。- togetter
GADTによるHaskellの型付きDSLの構築 - プログラミングの実験場


*1

シューティングゲームを作ったことがある人ならだいたいは勘でわかりますが

幸せすぎると 恐くなるの コップの水が溢れだすように

音楽


HOME MADE 家族 - Love is... feat. Ms.OOJA (Short ver.)