・クロージャを作るには、myとリファレンスの知識が必要になる。
・このページでは、 [07/17.
局所化]
[07/18.
リファレンスとは]
[07/19.
リファレンス生成]
[07/20.
デリファレンス]
のことを全て理解できてると言う仮定の元で話を進めている。
#(perlにおける)クロージャの定義
[実行時に、自分に対するリファレンスが生成された時、
自分の外部にあったレキシカル変数の実体をそのまま覚えている無名のサブルーチン]
・長たらしい定義なので、一つづつ分解して考えてみる。
定義の分解1:クロージャはどうも「無名サブルーチンの一種」らしい。
・よくよく読むと「クロージャは無名サブルーチンへのリファレンス(の実体)」
であることが分かる。
---
定義の分解2:レキシカル変数を参照出来る場所でサブルーチンのリファレンスが生成されなくてはならない。
---
定義の分解3:そのレキシカル変数は、そのサブルーチンの外側になくてはならない。
・「外部にあったレキシカル変数」と定義にある。
---
定義の分解4:サブルーチン内でもそのレキシカル変数が使われていなくてはならない。
・「そのまま覚えている」って書いてあるので、サブルーチン内にそのレキシカル変数がないと、
そもそもサブルーチンはその変数を知らないことになる。
---
・…以上を満たしている無名のサブルーチンは、クロージャになることが出来る。
---
・実際にクロージャになるには。
定義の分解5:実行が、そのレキシカル変数のスコープを抜けた時にそのサブルーチンはクロージャになる。
・「そのまま覚えている」とあるので、スコープを抜ける必要がある。
レキシカル変数のスコープ内で、そのレキシカル変数を参照できるのは当たり前のコトで、
それは「そのまま」もクソもない。
---
定義の分解6:そのレキシカル変数もブロックなどのスコープの中になければならない。
・もし、そのレキシカル変数が一番外側のスコープにあったなら、イツまで経っても「定義の分解5」が満たされない。
・クロージャの作成法は工夫すれば多くあるが、
無名サブルーチン自体は式の中で定義されるので、(かなり横暴な発言だけど)式が置ける場所ならばどこでも作る事が出来る。
・一見複雑そうに見える定義を満たすだけで良いなら、以下のようにすればクロージャになる。
{
my $scalar = 100;
our $closure = sub {
return $scalar;
};
}
・これでクロージャの定義を満たす動作をする。実際の処理は以下のようになる。
1.ブロックの中に入る。(スコープ定義のためのブロック)。
2.レキシカル変数が生成され、100が代入される。
3.$closure に、無名サブルーチン(のリファレンス)が代入される。
4.この無名サブルーチンは先ほどのレキシカル変数が使用され、その値が返るサブルーチンになっている。
5.最後にブロックを抜け、$scalarが破棄……されない。
6.ここで一つでも参照法が残ってる場合、その「実体」は破棄されない、と言う「my」のルールが適用されている。
7.この時点をもって、$closureは、クロージャの定義を満たされたことになる。
8.なお、これ以後$closureをデリファレンスすると、定数(100)が返ってくる。
・キーポイントは、myのおかげで実体が消滅しないことと、そのレキシカル変数には以後、直接手出しが出来なくなること。
・外側を裸のブロックで囲んでいるが、これは私が(この説明用に)考え出した物で、実践ではあまり使われない物と思われる。
・備考1::グローバル変数$closureは、レキシカル変数でも構わない。デリファレンスしたい位置で消滅してなければそれで良い。
・備考2:定数がほしいだけならクロージャは必要ない。
例えば、以下の二つの方法はどちらも「3.14」と言う定数を定義する。
#▼定数を定義するのに一番良い方法
sub PI () {3.14} #軽い上、単に「PI」と書くだけで3.14を意味する。
#▼おそらく次に良いと思う方法
$PI = \3.14; #リテラルのリファレンス取得
▼ 実行中に関数の内容を定義
・クロージャはサブルーチンなので、普通は何かの処理をする。
その処理だがクロージャの場合、レキシカル変数によって処理の実行内容が変わる(決まる)。
それは一般的な実用では、無名サブルーチンが取り扱う値(レキシカル変数)を実行中に決定するからだ。
・また、クロージャ自体もデリファレンスされた時に値を受け取り、値を返すのは普通である。
・つまりクロージャは「生成された時のレキシカル変数によって自身の処理内容が決まる関数」と言う考え方は一般的だが、
生成された時と呼び出された時の「時間差のある二段階の値を扱える処理方法」と言う考え方もできる。
・以下レキシカル変数の内容を不明な物にし、クロージャが引数を受け取れる記述になっている。
#便宜上ここを「A地点」と呼ぶ。
{
## $::unknownは今までの処理の過程でどうなってるか分からない
my $scalar = $::unknown;
our $closure = sub {
return $_[0] + $scalar;
};
}
・この無名関数は、A地点で$::unknownが持っていた値と、クロージャがデリファレンスされた時の第1引数の合計を返す関数になっている。
▼ クロージャを返す関数
・クロージャの生成法として、最も汎用性が高く、
また使いやすいのがクロージャを返す関数になる。
また、一般的には全てこの記述です。これ以外のクロージャ生成の記述や説明は見たことがありません。
sub function {
my $scalar = $_[0];
my $closure = sub {
my $scalar_c = $_[0] + $scalar;
return $scalar_c;
};
return $closure;
}
・非常に手っ取り早く言うと、function関数は先ほどの記述を便利にしただけの記述と表現できる。
functionから返される無名関数は、「function関数に渡した第1引数」と、
「無名関数を呼び出した時の第1引数」の合計を返す。
・クロージャの強みは、多量に関数を生成出きる点にもある。
ポイントは、functionを呼び出すたびにmy変数の$scalarが再生成されて、
別の変数になっている所にある。
複数回にfunctionを実行しても、
それぞれの無名関数が参照しているのは全て違う$scalar(実体)になっている。
#▼上記と全く同じ。
sub function {
my $scalar = $_[0];
my $closure = sub {
my $scalar_c = $_[0] + $scalar;
return $scalar_c;
};
return $closure;
}
#▼クロージャ生成
$closure_1 = function(100);
$closure_2 = function(200);
$closure_3 = function(300);
#▼クロージャ使用
$result_1 = $closure_1->(10);
$result_2 = $closure_2->(10);
$result_3 = $closure_3->(10);
#▼いらなくなった
$closure_1 = undef; #もうこの関数いらないや
$closure_2 = undef; #内部的に関数も変数も消滅
$closure_3 = undef;
・($result_1, $result_2, $result_3) には、(110, 210, 310)が代入されている。
▼ その他
・クロージャが使っている変数は、クロージャ自身の手によって変えることもよくある。
例えば、以下のクロージャは、呼び出されるたびに返す値が変わる(倍になる)。
{
my $scalar = 1;
our $closure = sub {
$scalar = $scalar + $scalar;
return $scalar;
};
}
・これは一般的なのか分からないが、クロージャの動作を後から「再変更/参照」する手段が存在する。
ただし、そうなるとクロージャを「関数」とは呼びにくくなる面がある。以前ちょっと思いついて作ってみたのだが、個人的にはこれはイカサマなんじゃないかと思ってる。
{
my $scalar = 1;
our $closure = sub {
my $scalar_c = $scalar + $_[0];
return $scalar_c;
};
our $ref = \$scalar;
}
▼ ブラックボックス
・クロージャは、外部から参照不能な動的なデータの格納場所としても非常に有意義に使うことができる。
・良い参考例が思いつかないので記述はなし。
▼ 動的な生成の例
・クロージャは以下の時にも有意義に使える。
1:実行時にならないと関数の動作を決められないとき。
2:似たような多量の関数がほしい時
この二つに、とてつもない威力を発揮する。
・今回はこの二つを同時に満たす処理を考えてみる。
・例えばRPGゲームの魔法を作りたくなったが、魔法は多量に必要と予想される。
・一つの魔法につき、一つの関数を作っても良いが……クロージャのほうが良さそうかも?
1. 一つのクロージャから多量の魔法を生成したほうがシンプルになり記述が見やすい。(100の魔法が一つの関数になるかも?保守が楽)
2. この方法のほうが魔法データの打ち込みが楽でもある。(まぁ…たぶんね。似たような魔法とかね、追加が楽そうだしね)。
3. 魔法自体の関数だけでなく、魔法周辺の処理が整理できて綺麗になる。(たぶんね、呼び出す時とかね、その辺ね)
4. 実行時に魔法を自由に定義できるため、あらかじめ決められた魔法ではなく、無限大の魔法を産み出すコトも可能になる。
#▼魔法を産み出すサブルーチン(非常に簡易的な記述)
sub magic {
my %magic;
#▼魔法の定義を作成
$magic{'魔法名'} = shift; #魔法の名前
$magic{'属性'} = shift; #炎とか氷とか?
$magic{'数値'} = shift; #ダメージとか回復の量?
#▼魔法の実行
return sub {
#ここで魔法実行の処理
#▼実行後のコメント
return " $x は $magic{'魔法名'} をぶっぱなした。" .
" $y たちは $d 円相当の損害をうけた。\n ";
};
}
・まぁ適当な記述です。
・一つの魔法生成機(関数)から、汚くせずにどの程度性質の違う魔法を生み出させるように出来るかは、プログラミングセンスによると思われます。
・つまり、似たような概念の関数を複数必要とする時は、一つの関数から生成したほうが良いと思うってことです。
・また、例えばゲームのような、臨機応変の処理がほしい時は、実行中に関数を生成するようにしたほうが、なにかと保守と更新が楽になると思います。
(外部のデータや、入力により関数の動作が決定される時、など)。
▼ 外部の変数を使用
・また、クロージャは(普通の)サブルーチンに外部の変数を参照されたい時にも有意義に使われる。
・通常のサブルーチンは一般的に、ルーチン内の変数だけで処理を完結させないと、作法やマナーの違反とされてしまう。
しかしクロージャの場合、そのクロージャ用にスコープが定義されたレキシカル変数が用意されるので、外部の(その)変数を使っても問題にならない。
・ルーチン内で外部の変数を参照したい場合、わざわざクロージャにしてしまう手がある。
・良い例が思いつかないので記述はなし
……と言うか、クロージャはもれなく外部のレキシカル変数を必ず使うので、わざわざ例を用意する必要はないですよね。
▼ オブジェクト指向
・クロージャは、オブジェクト指向モジュールでも非常に有意義に使用されている。
・当サイトではオブジェクト指向は一切説明されてません。他のサイトを当たって下さい。しっし。