呪文詠唱!F# #ML_study

ML勉強会 #2 - connpass

発表してきました。

要約

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

はじめに

突然ですが問題です。 

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

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

正解

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

中身

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

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

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

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

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

無詠唱

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

詠唱 { () }

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

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

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

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

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

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

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

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

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

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

補足?

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

まとめ

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