PHP でクロージャに use が必要理由を考えてみた
◆ 変数宣言構文がない言語だと 代入したときに外側を書き換えれば良いのか ローカルスコープに変数作れば良いのかがわからない
◆ use した変数は一種の引数みたいな扱いにして クロージャを定義した場所のスコープの変数は見れなくすれば 迷うことはなくなる
◆ use した変数は一種の引数みたいな扱いにして クロージャを定義した場所のスコープの変数は見れなくすれば 迷うことはなくなる
PHP の use
PHP ってクロージャを定義しても よくあるレキシカルスコープではなくて自分で use を使って渡した変数のみが使用可能という特殊なものですよくある仕様じゃなくて独自の使いづらい仕様にするのはいつもの PHP なのでもう何も言いませんが どうしてこうしたのかは気になります
$value = 10;
$result = array_map(function($e){
return $e * $value;
}, [1, 2]);
print_r($result);
これは $value を参照できないので
PHP Notice: Undefined variable: value
と Notice が出ます
use を使うことで参照できます
$value = 10;
$result = array_map(function($e) use($value){
return $e * $value;
}, [1, 2]);
print_r($result);
Array
(
[0] => 10
[1] => 20
)
use だけでは参照渡しではなく引数の値渡しと同じです
書き換えても関数の中だけで use で指定した変数の方は変わりません
use したものがオブジェクトで そのプロパティを書き換えた場合は変わります
宣言がないから?
JavaScript だとこう書けますconst value = 10
const result = [1, 2].map(e => {
return e * value
})
console.log(result)
// [10, 20]
外側の value は気にせず関数の中に value という変数を新たに作りたい場合はこうなります
let value = 10
let result = [10, 20].map(e => {
let value = 100
return e * value
})
console.log(value, result)
// 10 [1000, 2000]
let がないと
value = 10
result = [10, 20].map(e => {
value = 100
return e * value
})
console.log(value, result)
// 100 [1000, 2000]
value が外側を書き換えてしまいます
PHP だと let や var にあたる変数宣言キーワードはありません
最初に代入すればそこで変数が作られます
そうなると クロージャ内での代入は新しくスコープ内に変数を作るのか 外側を書き換えるのかわかりづらく混乱します
完全にスコープを切り離して use を使って渡した変数のみ参照可能にして 外側を見れないとしてしまえば 代入時にどうなるかはわかりやすくなります
こういう理由で use 使うことになったのかなと思っています
使いづらいですけど
他の言語は
考えてみれば 宣言キーワードの要らない言語って PHP の他にもあると思います他の言語はどうなんだろうとみてみました
JavaScript
JavaScript は宣言なしにもできますが 基本は var/let/const を書きます書かない場合はグローバルスコープになるので 宣言なしではクロージャ内のローカルスコープに変数は作れません
静的スコープ上に存在→それを書き換え
存在なし→グローバルに代入
となります
Lua
JavaScript と同じで宣言なしはグローバルスコープの書き換えですローカルスコープにするには local キーワードを使います
Python3
Python だと関数内関数とラムダ式があります関数内関数の場合は 代入はローカルスコープになります
def fn():
value0 = 1
value1 = 2
def sub():
value1 = 3
value2 = 4
print(value0, value1, value2)
sub()
print(value1)
fn()
# 1 3 4
# 2
value0 の 1 は参照できますが value1 で書き換えても外側は 2 のままです
また value2 は外側に存在しないので参照できません
ラムダ式は関数の body に式をひとつだけを書けるものです
Python では代入は式ではなく文なので変数を代入することはできません
value = 10
result = map(lambda e: e * value, [1, 2])
print(list(result))
# [10, 20]
こうすれば一応それらしいことは可能です
def let(v, fn):
return fn(v)
value = 10
result = map(lambda e: let(100, lambda value: e * value), [1, 2])
print(list(result))
# [100, 200]
Ruby
value = 10
result = [1, 2].map { |e|
e * value
}
p result
# [10, 20]
Ruby だと外側のスコープに変数があればそこを書き換えます
value = 10
result = [1, 2].map { |e|
value = 100
e * value
}
p result
# [100, 200]
p value
# 100
; の後に変数名を指定することで ローカルスコープの変数を宣言することもできます
value = 10
result = [1, 2].map { |e; value|
value = 100
e * value
}
p result
# [100, 200]
p value
# 10
こうすればローカルスコープになるので外側は書き換わりません
ただし先にローカルスコープに変数を宣言するので 代入前に参照しても外側の値を参照できません
R
value = 10
result = Map(function(x) x * value, 1:2)
print(result)
[[1]]
[1] 10
[[2]]
[1] 20
代入はローカルスコープで外側には影響しません
value = 10
result = Map(function(x){
value = 100
x * value
}, 1:2)
print(result)
print(value)
[[1]]
[1] 100
[[2]]
[1] 200
[1] 10
ローカルスコープではなく外側の値を更新したい場合は 代入演算子を変えます
value = 10
result = Map(function(x){
value <<- 100
x * value
}, 1:2)
print(result)
print(value)
[[1]]
[1] 100
[[2]]
[1] 200
[1] 100
Julia
Julia は JavaScript や Lua に近いですが 基本は宣言不要です書かなくてもグローバルにはなりません
必要なときのみ global, local キーワードつけます
value = 10
result = map(e -> e * value, [1, 2])
println(result)
# [10,20]
println(value)
# 10
外側に変数があれば外側を書き換えます
function fn()
value = 10
result = map(function(e)
value = 100
e * value
end, [1, 2])
println(result)
println(value)
end
fn()
# [100,200]
# 100
local を使うことで外側に同名の変数があっても新しくスコープに変数を作ります
function fn()
value = 10
result = map(function(e)
local value = 100
e * value
end, [1, 2])
println(result)
println(value)
end
fn()
# [100,200]
# 10
Elixir
value = 10
result = Enum.map([1, 2], fn(e) -> e * value end)
IO.inspect result
# [10, 20]
value = 10
result = Enum.map([1, 2], fn(e) ->
value = 100
e * value
end)
IO.inspect result
# [100, 200]
IO.inspect value
# 10
変数は immutable なので外側を書き換えることは出来ずローカルスコープになります
mutable 変数がないのでたぶん外側を書き換えることはできません
Hack
PHP と似ている Hack ですが function でクロージャを作ると PHP と一緒で use を使います<?hh
$value = 10;
$result = (vector {1, 2})->map(function($e) use ($value){
return $e * $value;
});
print_r($result);
HH\Vector Object
(
[0] => 10
[1] => 20
)
Hack にはラムダ式もあります
こっちだと use なしで外側を参照できます
<?hh
$value = 10;
$result = (vector {1, 2})->map($e ==> {
return $e * $value;
});
print_r($result);
HH\Vector Object
(
[0] => 10
[1] => 20
)
内側で代入してもローカルスコープになり 外側は変わりません
<?hh
$value = 10;
$result = (vector {1, 2})->map($e ==> {
$value = 100;
return $e * $value;
});
print_r($result);
print_r($value);
HH\Vector Object
(
[0] => 100
[1] => 200
)
10
Hack のラムダ式は PHP の use で変数をキャプチャするのを楽にしてくれるもので ラムダ式が暗黙的に use して変数を使えるようにしてくれます
しかし use は値としてのキャプチャのみで 参照してのキャプチャはサポートされていません
外側を書き換える必要があるなら function を使う構文で use を自分で書いて参照にする必要があります
https://docs.hhvm.com/hack/lambdas/introduction
まとめ
ローカルスコープになるのか外側のスコープになるのか 言語ごとにバラバラでしたね好きな方を選べるものは便利ですが どっちがどっちだっけと思うこともあります
PHP の use だと 外側と切り離されてるのがすぐにわかるのでローカルスコープだとほぼ迷うことがないというメリットはあるとおもいます
普段書くのがすごく面倒ですけど