memo

ちょっとしたメモ書き(個別表示)

ループの一時変数 : (2012/03/28)
ループの一時変数の話
以下のうち仲間外れはどれか?
JavaScript
var tmp = [];
for(var i = 0; i < 5; i++){
tmp.push(function (){ return i; });
}
for(var n = 0; n < tmp.length; n++){
print(tmp[n]());
print("\n");
}

Python
tmp = [];
for i in range(0, 5):
tmp.append(lambda : i)

for f in tmp:
print f()

OCaml
let tmp = ref [] in
for i = 0 to 4 do
tmp := (fun () -> i) :: !tmp
done;

(List.iter
(fun f ->
print_int (f ());
print_newline ())
(List.rev !tmp))

JavaScriptの例とPythonの例はどのクロージャも最後の一時変数の値を出力するが、OCamlに関してはそれぞれのクロージャが生成された時点での一時変数の値が出力される
(JavsScriptはGoogle V8で確認、ただしprint関数は未定義なので適当に定義)

OCamlのforループは一時変数を破壊的に更新しないというのを最近知った。
(というか、今まであまり気にしてなかっただけなんだけど)
これは「カウンタ付き再帰を形式化したものがforループ」という考え方であり、同じ変数名でありながら、ループ内の処理が実行されるたびに新たな変数束縛を作り出していることになる(一つの変数束縛を破壊的に使いまわすのではなく)
特に意味は無いがあえて上記のループを再帰で書くなら以下のようになるだろうか……
let rec for_rec = (fun i last f ->
if i <= last then ((f i); for_rec (i + 1) last f)
);;

let tmp = ref [] in
(for_rec 0 4 (fun i ->
tmp := (fun () -> i) :: !tmp
));

しかし破壊的代入が明示されているJavaScriptの例はともかく、Pythonのこの挙動は変えた方が良いと思うんだけど、スコープ回りの仕様変更はシビアだから変えるに変えられないんだろうか。
でもこれを変えようと思ったらfor statement単体じゃなくてブロックスコープのところから考えないといけないのかな。
Rubyも1.6系(1.8系も?)ぐらいまではイテレータ周りのスコープがおかしかったし(今は直ってるはず)
言語設計者にとってこの辺りの仕様は鬼門なんだろうな。
ちなみにC#のforeachの一時変数も議論があって仕様が変わるらしい(もちろん非互換な変更)
大変だね。

ちなみにJavaScriptは以下のようにするとブロックスコープもどきが可能
var tmp = [];
for(var i = 0; i < 5; i++){
with ({ i: i }) {
tmp.push(function (){ return i; });
}
}
with statementで指定したオブジェクトをスコープに割り込ませるという力技。
これとは別にlet statementを導入してブロックスコープを制御するという話があったと思ったんだけど手元のV8では未対応のようだ。
標準であるECMA Scriptの仕様にも見当たらないようなので調べてみたら、どうやらMozillaの独自拡張らしい。
こういう用途ならwithよりもletの方が良さそうなので、標準にも導入してほしいところだけど。
そもそも根本的にはvarのスコープの範囲区分が大域と関数内しかないのが原因だからそっちで揉めてるんだろうか?
(一応letはFuture Reserved Wordsに含まれているので、将来的には標準化の可能性があるみたい)
やはりスコープは鬼門だ。

そういえばECMA Scriptの最初の策定者はGuy Steele Jr.だったのか……
今日初めて知りました。


ちなみにPythonはそういう機能も無いみたいなので無名関数使って引数で割り込ませるしかないかも
tmp = [];
for i in range(0, 5):
tmp.append((lambda i : (lambda : i))(i))
これは微妙だ

: back