(hatena (diary ’Nobuhisa)) このページをアンテナに追加 RSSフィード Twitter

10/03/21 :

[][]Quotation で Meta-Programming !

F#は自分の中に眠る力を引き出すべく、Lisp長老に会いに行ったのだった。

f:id:Nobuhisa:20100320032234j:image

Lisp like meta-programming ・・・!

いくつか例をあげてみよう。

(実行にはPowerPackが必要です)

階乗

open System
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Linq.QuotationEvaluation
open System.Runtime.Serialization.Formatters.Binary

// Expr の保存とロードを担当
type ExprManager() =
    static let formatter = new BinaryFormatter ()
    static member Save<'T> fileName (e : Expr<'T>) =
        formatter.Serialize ( System.IO.File.Create fileName, box e )
    static member Load<'T> fileName =
        formatter.Deserialize ( System.IO.File.OpenRead fileName ) |> unbox<'T Expr>

// 普通のfac
let rec fac = function
| 1 -> 1
| n -> n * fac (n - 1)

// 式を生成する再帰関数
let rec meta_fac = function
| 1 -> <@ 1 @>
| n -> <@ n * %(meta_fac (n - 1)) @>


let fac5 = meta_fac 5


// C++やCommon Lispのようにコンパイル時に展開できないので、事前に保存してやってもいいかもしれない
// しかしロードの方がコストがかかるかもしれない・・・
//let pre_fac3 () = ExprManager.Save "sample.txt" (meta_fac 3)

// ロード(似非プリプロセス)してやる
let fac3 = (ExprManager.Load<int> "sample.txt").Compile ()


[<STAThreadAttribute>]
do
    printfn "%A" <@ 5 * (4 * (3 * (2 * 1))) @> // これと同じになれば成功!
    printfn "%A" fac5
    printfn "\n"
    printfn "%d" <| fac 5
    printfn "%d" <| fac5.Eval ()
    printfn "%d" <| fac3 ()

Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
      [Value (5),
       Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
             [Value (4),
              Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
                    [Value (3),
                     Call (None,
                           Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
                           [Value (2), Value (1)])])])])
Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
      [Value (5),
       Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
             [Value (4),
              Call (None, Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
                    [Value (3),
                     Call (None,
                           Int32 op_Multiply[Int32,Int32,Int32](Int32, Int32),
                           [Value (2), Value (1)])])])])


120
120
6
続行するには何かキーを押してください . . .

残念ながらパフォーマンスの面での期待はできなそうです。

コンパイル後の式はそこそこ速いものの、普通に書いた関数には及びませんでした。

色々オーバーヘッドがあるのかなぁ。

Lispではパフォーマンス向上のためにマクロを使って式を展開することもありますが、今の時代に気にするほどのことではないですね・・・。


and オペレータ

全てが真の場合のみ真を返すオペレータを書いてみよう。

先頭から見ていって、偽が見つかった時点で評価を中止する。

let (><) (a : Expr<bool>) (b : Expr<bool>) = <@ if %a then %b else false @>

let and_test = <@ true @> >< <@ false @> >< <@ 1 / 0 = 0 @>

[<STAThreadAttribute>]
do
    printfn "%A" <| <@ if (if 1 = 1 then 2 = 2 else false) then 3 = 3 else false @>
    printfn "%A" <| ( <@ 1 = 1 @> >< <@ 2 = 2 @> >< <@ 3 = 3 @> ) // 上と同じになるかな!?

    printfn "%A" <| and_test.Eval () // 例外は発生しない

IfThenElse (IfThenElse (Call (None, Boolean op_Equality[Int32](Int32, Int32),
                              [Value (1), Value (1)]),
                        Call (None, Boolean op_Equality[Int32](Int32, Int32),
                              [Value (2), Value (2)]), Value (false)),
            Call (None, Boolean op_Equality[Int32](Int32, Int32),
                  [Value (3), Value (3)]), Value (false))
IfThenElse (IfThenElse (Call (None, Boolean op_Equality[Int32](Int32, Int32),
                              [Value (1), Value (1)]),
                        Call (None, Boolean op_Equality[Int32](Int32, Int32),
                              [Value (2), Value (2)]), Value (false)),
            Call (None, Boolean op_Equality[Int32](Int32, Int32),
                  [Value (3), Value (3)]), Value (false))
false
続行するには何かキーを押してください . . .

Quoteしているため、遅延評価的な扱いになる。そのため 1 / 0 = 0 はその場では例外をスローしない。

普通の関数で定義してしまうと、必要のないものまで評価してしまうのでif系関数としてはよろしくない。*1


感想

Quotationは実行時に限るのでC++テンプレートメタプログラミングなどとは毛色が違います。また、あらゆる式を記述できるわけではないので、柔軟性に欠けます。例えば型を定義できないしobject expressionなども使えない。


そのメタプログラミングの考え方はどちらかというとCommon Lispのように式を展開してゆくスタイルに近いけれども、強力さには雲泥の差があるように思います。Lispは式が単純な上に動的型付けであることも手伝って、自在に自身を記述することが出来る。おまけにコンパイル時・実行時・読み込み時(リードマクロ)など色々なタイミングで式を展開することも出来る。


パフォーマンス向上目的での利用もあまり意味がなさそうなので、独自のシンタックスシュガーを作るか、Linq to SQLのように式を解析して何かを生み出してやるか、などに利用範囲は限られそうです。ここぞ!というところで活きてくれるのを期待しているけど、現時点だとあまりアイディアが浮かばない・・・。


Computation ExpressionはQuotationでも利用可能みたいなので、その辺から何か面白い使い方でも出てくるのでは、と漠然と思っています。

*1:lazyで回避できますが

zeclzecl 2010/03/24 00:35 おおー、Computation ExpressionはQuotationでも利用可能なんですか?それは興味深い!
少し触わって、C#のExpression Tree(式木)と同等のことくらいしか出来ないのかなあと、
勝手に思い込んでいましたが、F#のQuotationの方がいろいろモリモリと出来そうな感じですね。

この記事のサンプルコードを動かしてみようと思ったのですが、
VS2010 Beta2ではだめだったので、さきほど重い腰をようやく動かしてVS2010 RCを入れました(遅
ところで・・、最近ドラゴンボールにハマってるようですねw
勉強会のセッションの中にもでてきちゃったりするのかな。

NobuhisaNobuhisa 2010/03/25 17:14 C#のExpression Treeは使いづらくてメタプログラミングする気が起きませんね・・・苦笑
極端に言えば表現能力は同等かと思いますが、Quotationの方が個人的に書きやすいです。

あら、β2ではダメでしたか・・・どこがひっかかったんだろう。。
ドラゴンボールは常にマイブームです!クリリンのことかー!

zeclzecl 2010/03/25 22:38 C#のExpression Treeは…確かに。マゾ的な雰囲気ですね(爆
あれは頭の回転の悪い自分にはかなりきつい感じです。
脳トレにはよさそうですが(笑
それに比べてF#のQuotationは直感的ですし、記述性・可読性ともに抜群ですね。
これは・・・流行る!(?)

あ、Beta2でダメだったのではなくて、
単にF# PowerPackが入ってない環境だったからぽいです。すいません。

>クリリンのことかー!
ベジータ「へっ、きたねえ花火だ」
ΩΩΩナ ナンダッテー!!

NobuhisaNobuhisa 2010/03/27 19:38 Quotationも型の記述ができるようになれば表現力が格段に上がるのでしょうけど、
そうするとインテリセンスの問題も出てくるし、なかなか難しいのかな・・・。
せめてObject Expressionsは対応して欲しかったです。

PowerPackが必要だとの記述が抜けていたので追記しておきました。すみません><

ちなみに私の戦闘力は53万です。よろしくお願いします。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/Nobuhisa/20100321/1269175474