このエントリは F# Advent Calendar 2011 第17回目の参加記事です。狙ったわけではありませんが去年と同じ17番手です。元ネタは[twitter:@gab_km]さんの東京F#読書会 20110930に参加したとき、UNIX V6で使われているK&R以前の古いC言語のlexerを書いていて、シンボル名は最初の1文字だけ数字が使えないという制約をどう実装するか考えていたことに由来します。
はじめに
データが1つずつ渡される状況での評価関数について考えました。
まず、3より小さいかを判定します。後の例と比較しやすいよう、無名関数にはしていません。
let data = [ 1; 2; 3; 4; 5; 3; 1 ] let test f = for v in data do printfn "%d -> %b" v (f v) let eval v = v < 3 test eval
副作用の導入
※ 今回は副作用の是非については論じません。
最初だけ3以上、それ以降は3より小さいかの判定について考えます。評価関数を差し替え可能とするため、インターフェースは変えないものとします。
クラス
最初に思い付いたのはクラスで状態を持つ方法です。
type Eval() = let mutable first = true member x.eval v = if first then first <- false v >= 3 else v < 3 test (Eval().eval)
クロージャ
フィールドの代わりにローカル変数をキャプチャしたクロージャに置き換えてみます。mutableをrefに書き換えただけで、それ以外はあまり変わりません。
let eval = let first = ref true fun v -> if !first then first := false v >= 3 else v < 3 test eval
これだとevalは一度しか使えないので、関数化して再利用できるようにします。
let eval() = let first = ref true fun v -> if !first then first := false v >= 3 else v < 3 test (eval())
クラスでもクロージャでも同じことができました。
※ 似たような話題は id:n7shi:20100913 でも取り上げました。どちらを使うかは好みの問題もあるため、ここでは論じません。
追記
[twitter:@nagat01]さんより、副作用のない例を教えていただきました。今回私はtestを固定化するという発想で副作用を導入しましたが、そもそもtestを固定化する必然性が薄かったです。
静的ローカル変数
先ほどevalを再利用するために関数化しましたが、敢えてシングルトンにする場合は関数化しない方が好都合です。
この技はC言語の静的ローカル変数を直訳するのに使えると気付きました。
C言語
int test() { static int a = 0; return ++a; }
F#
let test = let a = ref 0 fun () -> a := !a + 1; !a
testを関数化するとインスタンス的な挙動になるわけですが、この挙動を見て、AppDomainがグローバル(C#でのpublic static)すらインスタンス化すると知ったときの衝撃を思い出しました。レイヤが異なるのでちょっと乱暴な感想ですが。
※ CLIの実装要件としてAppDomainとJScript.NETが挙げられているのは、mono meetingでid:atsushienoさんに教えてもらいました。