スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

F#入門初級編第5回(クロージャー)

今回のお題は「クロージャー」です。
 
まずは、次のコードを見てください。
 
let a = 3
let f x = x + a
 
関数fがどういう関数であるかというとこの場合、引数に3を足して返す関数です。
しかし、fという関数をよくみてみると、引数経由の値xと、引数経由でない値aを使って定義されています。引数経由でない値はどう決めるかというと、その関数が作成される時にスコープ内の一番近いaを探しでその値が採用されます。この場合aがimmutableな値なので、同じaを参照している限りfはどこで作成しても同じ働きをする関数なのですが、immutableでない値、例えばref型を使うと話は異なってきます。
 
次の例を見てください。
 
let a = ref 0
let k = List.map (fun x -> a := !a + 5 ;x * !a) [1;2;3]
 
((注);はunit型の値を返す式を他の式につなげて一行にする時に使います。
まずはkがどういうものになるかを予想してください。
答えは
val k : int list = [5; 20; 45]
というようになります。
 
まず、1に適用する関数を作成するとき,この関数においてaの状態は0です。よって、これに5を加えて5となるので、それに引数をかけて返す関数が作成されます。これを1に適用して5が最初の適用結果です。
つぎに、2に適用する関数を作成するとき,この関数においてaの状態は5です。よって、これに5を加えて10となるので、それに引数をかけて返す関数が作成されます。これを2に適用して20が次の適用結果です。
つぎに、3に適用する関数を作成するとき,この関数においてaの状態は10です。よって、これに5を加えて15となるので、それに引数をかけて返す関数が作成されます。これを3に適用して45が最後の適用結果です。
 
大切なのは関数が作成される時点で引き数以外の外部の値を参照する場合、作成される時点での、外部の値を取り込んで(キャプチャーして)関数が作成されるということです。
 
この手法は関数内で関数を定義するという形で、よく使われます。例えば次の例をみてください。
 
let counter  =                 //#1
    let a = ref 0              //#2
    (fun () -> a:= !a + 5;!a)  //#3
 
 
まずこれはcounterという名前の関数を定義していることに留意してください。返り値は関数定義の最後に評価される値ですから、#3の「unit型を引数にとり、!aの値であるint型の値を返す関数」が返り値になります。
よってcounterが関数の名前で、この関数を実行するためには、unit型の値()を渡せばいいことになります。よってcounter ()で関数の実行です。
 
それではF#Interactiveに読み込ませてみます。
 
> let counter  =               //#1
    let a = ref 0              //#2
    (fun () -> a:= !a + 5;!a)  //#3
;;
 
val counter : (unit -> int)
 
関数を実行してみます。
 
> counter ();;
val it : int = 5
 
> counter ();;
val it : int = 10
 
ちゃんと毎回5増加していることが分かります。
 
今度は、説明の補助のためにprintfnを入れてみました。
 
let counter  =                 //#1
    printfn "今からaを定義します。"
    let a = ref 0              //#2
    printfn "aの定義が終わりました。"
    (fun () -> a:= !a + 5;printfn"aの値を5増やしました";!a)  //#3
 
printfn "今からcounterを呼び出し引数()を与えて値を表示します。"
 
printfn "%d" (counter ())
 
printfn "%d" (counter ())
 
これで実行すると次のようになります。
 
今からaを定義します。
aの定義が終わりました。
今からcounterを呼び出し引数()を与えて値を表示します。
aの値を5増やしました
5
aの値を5増やしました
10
 
すなわち、関数counterが引数()で呼び出されるたびに、作成実行されるべき#3にある関数はスコープ内にある#2にあるaの値を調べにいき、その値を使って作成され実行されることになります。
#3で定義される関数は、関数外部の値(#2)をキャプチャーして(取り込んで)作成されるということです。
このように、関数(例では#1)の内部で、引数経由ではない外部の値(例では#2)をキャプチャーして作成される内側の関数(例では#3)をクロージャーといいます。
(F#Interactiveではクロージャーは関数の型全体が()で包まれます。(例えばcounterの型は(unit->int)となってます。)
 
一つ注意しておきたいのは、counterの外部からは、aへのアクセス手段がないということです。
すなわち、aという値が関数内に隠蔽されているという点です。
 
さて今度はカウンターの初期値と増分を指定できるようにしたいと思います。
これは、部分適用を利用してつぎのように実現できます。
 
> let counter init delta =
    let a = ref init
    (fun () -> a:= !a + delta;!a);;
 
val counter : int -> int -> (unit -> int)
 
部分適用して、二つのカウンターを作ってみます。
 
> let t0 = counter 0 1;;
val t0 : (unit -> int)
 
> let t1 = counter 10 -1;;
val t1 : (unit -> int)
 
使ってみます。
 
> t0 ();;
val it : int = 1
 
> t0();;
val it : int = 2
 
> t1();;
val it : int = 9
 
> t1();;
val it : int = 8
 
t0とt1ではそれぞれ別の「返り値を返す時参照する外部変数」が作られて使用されていることに留意してください。
 
ここでもう一つ注意ですが、「この返り値を返す時参照する外部変数」としてはmutableキーワードを付けた値は使用できません。
 
「関数内部への値の閉じ込め」と上でやった手法をみると、なんだかクラスの定義に似ているような気がします。(privateなフィールド変数と、クラスのインスタンス化)実際クロージャーのテクニックは、クラスの代わりに使われるようですし、「オブジェクトは貧者のクロージャー」対「クロージャーは貧者のオブジェクト」論争なるものもあるそうです。
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

コメントの投稿

非公開コメント

プロフィール

T GYOUTEN

Author:T GYOUTEN
F#と英単語とフリーソフトと読書に興味があります。
ホームページでフリーソフトも公開しています。どぞ御贔屓に。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
フリーエリア
フリーエリア
blogram投票ボタン
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QRコード
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。