2004-12-05 (Sun)
_ ワールド・イズ・ノット・イナフ
引越先ではテレビ朝日系列の番組を見ることができるので、日曜洋画劇場を(最初の方だけ)見た。 ソフィ・マルソーってちょっと石野真子に似てるよね。
2011-12-05 (Mon)
_ 退院
とりあえず、やっと1か月の入院生活が終わって退院することができた。
でもまだ左腕の熱傷はあまり治っておらず、当面は週2回程度の通院で様子を見ることに。
主治医の先生の話だと、今の状態で植皮してもうまく行くとは限らないらしく、時間をかけて治す方針になった。 きれいに治るといいんだけど…。
2018-12-05 (Wed)
_ 特殊変数のスコープ
(この記事はRuby Advent Calendar 2018の参加記事です。)
Rubyはシンプルな文法が特長である。
Rubyには特殊変数と呼ばれる変数があって、見た目はグローバル変数だが、$_
や $&
などの一部の変数はローカル変数に似たスコープを持っている。「似た」というのは厳密には違いがあって、基本的にブロックローカルではなくメソッドローカルなのだが、スレッドのブロックではスレッド毎に固有の値を持つ。
t = Thread.start {
Thread.current.name = "sub"
$_ = "foo"
5.times do
puts "#{Thread.current.name}: #$_" #=> 5回とも「sub: foo」と出力
sleep(0.1)
end
}
Thread.current.name = "main"
$_ = "bar"
5.times do
puts "#{Thread.current.name}: #$_" #=> 5回とも「main: bar」と出力
sleep(0.1)
end
t.join
この挙動は、単純にメソッドローカルにするとスレッドセーフでなくなってしまうからである。 では、以下のように同じProcオブジェクトを別々のスレッドで実行するとどうなるか?
def with_special_var(x, &block)
Thread.current[:x] = x
block.binding.eval("$_ = Thread.current[:x]")
block.call
end
f = -> {
5.times do
puts "#{Thread.current.name}: #$_"
sleep(0.1)
end
}
t = Thread.start {
Thread.current.name = "sub"
with_special_var("foo", &f)
}
Thread.current.name = "main"
with_special_var("bar", &f)
t.join
実は処理系によって挙動が異なる。
CRubyの場合:
$ ruby -v t.rb
ruby 2.6.0dev (2018-11-28 trunk 66060) [x86_64-linux]
main: bar
sub: foo
main: bar
sub: foo
main: bar
sub: foo
main: bar
sub: foo
main: bar
sub: foo
JRubyの場合:
$ jruby -v t.rb
jruby 9.2.0.0 (2.5.0) 2018-05-24 81156a8 OpenJDK 64-Bit Server VM 9-Debian+0-9b181-4bpo91 on 9-Debian+0-9b181-4bpo91 +jit [linux-x86_64]
main: foo
sub: foo
main: foo
sub: foo
main: foo
sub: foo
main: foo
sub: foo
main: foo
sub: foo
JRubyでは両方のスレッドでProcオブジェクトを生成した環境の $_
が共有されるが、CRubyの場合はProcオブジェクトを生成した環境とスレッドを生成した環境が同一のメソッド呼び出し(上記の例ではトップレベル)だった場合、 $_
は各スレッド毎に別々の値を持つ。
CRubyでは特殊変数へのアクセスは以下のように実装されている。
static inline struct vm_svar *
lep_svar(const rb_execution_context_t *ec, const VALUE *lep)
{
VALUE svar;
if (lep && (ec == NULL || ec->root_lep != lep)) {
svar = lep[VM_ENV_DATA_INDEX_ME_CREF];
}
else {
svar = ec->root_svar;
}
VM_ASSERT(svar == Qfalse || vm_svar_valid_p(svar));
return (struct vm_svar *)svar;
}
lep
はlocal environment pointerでメソッドローカル変数が格納されている領域を指す。
ec->root_lep
は各スレッドの実行環境のトップレベルのメソッドローカル変数が格納されている領域を指す。
両者が同じ値の場合、各スレッドのトップレベルの特殊変数を格納する ec->root_svar
を返している。
JRubyの実装は読んでいないが、おそらく上記のように特殊変数のアクセス時に動的なチェックを行うのではなく、スレッド生成時にブロックが直接記述されている場合だけを特別扱いしているのだろう。
以下のようにProcオブジェクトとスレッドを別の環境で生成した場合は、CRubyでもJRubyと同じようにスレッド間で $_
の値が共有される。
def with_special_var(x, &block)
Thread.current[:x] = x
block.binding.eval("$_ = Thread.current[:x]")
block.call
end
def make_proc
-> {
5.times do
puts "#{Thread.current.name}: #$_"
sleep(0.1)
end
}
end
f = make_proc
t = Thread.start {
Thread.current.name = "sub"
with_special_var("foo", &f)
}
Thread.current.name = "main"
with_special_var("bar", &f)
t.join
Rubyはシンプルな文法が特長である。
2022-12-05 (Mon)
_ ...
を*, **, &
の構文糖にした話
Ruby Advent Calendar 2022の5日目の記事です。
Feature #19134で以下の文法エラーのケースを修正し、**
でキーワード引数だけ他のメソッドに渡すことができるように修正しました。
def foo(...)
bar(*) # OK
baz(&) # OK
quux(**) # 文法エラー
end
つまり、def foo(...)
はdef foo(*, **, &)
の構文糖になりました。
また、この修正により、以下のケースでbarにキーワード引数が渡っていなかったバグも修正されました。
def foo(*, **, &)
bar(...)
end
つまり、実引数の...
も*, **, &
の構文糖になりました。
普通は冗長な表現に対して後から構文糖を用意することが多い(例えば{x: x, y: y}
の構文糖として{x:, y:}
を追加する)ので、こういうケースは珍しいのではないかと思います。
きっかけ
Bug #19132の修正中に以下のコードが目にとまりました。
#ifdef RUBY3_KEYWORDS
#define idFWD_KWREST idPow /* Use simple "**", as tDSTAR is "**arg" */
#else
#define idFWD_KWREST 0
#endif
仮引数で...
を使用した場合は以下のように内部的な変数を用意していますが、Ruby 3ではキーワード引数も転送する必要があるので当然RUBY3_KEYWORDSというマクロはどこかで定義されているのだろうと思っていました。
static void
add_forwarding_args(struct parser_params *p)
{
arg_var(p, idFWD_REST);
#if idFWD_KWREST
arg_var(p, idFWD_KWREST);
#endif
arg_var(p, idFWD_BLOCK);
}
ところが念のため以下のようにidFWD_KWREST
が真の時にエラーにするようにしてコンパイルしてもエラーが発生しませんでした。
static void
add_forwarding_args(struct parser_params *p)
{
arg_var(p, idFWD_REST);
#if idFWD_KWREST
#error nokwrest
arg_var(p, idFWD_KWREST);
#endif
arg_var(p, idFWD_BLOCK);
}
ということは、
def foo(...)
bar(**)
end
はエラーになるのではないか、と思って試してみたところ、たしかにエラーになったので冒頭の問題に気付きました。
なぜ...
でキーワード引数が転送できていたのか
仮引数に...
が使用されていた場合、以下のようにruby2_keywordsというフラグが立つようになっていました。
static NODE*
new_args(struct parser_params *p, NODE *pre_args, NODE *opt_args, ID rest_arg,
NODE *post_args, NODE *tail, const YYLTYPE *loc)
{
...
args->ruby2_keywords = args->forwarding;
...
}
このフラグが立っていると、そのメソッドはModule#ruby2_keywordsが使用された場合と同様に、キーワード引数が*
で受け取られるようになります。
他のメソッドに*
を渡すとキーワード引数を含めて他のメソッドに転送できるので、**
が定義されていなくてもキーワード引数を転送できていたわけです。
つまり、修正前の
def foo(...)
bar(...)
end
は
ruby2_keywords def foo(*, &)
bar(*, &)
end
の構文糖になっていたのでした(*
はRubyレベルでは3.2で導入されましたが、それ以前のバージョンでも内部的には*
という変数名が使われていました)。
まとめ
Ruby 3.2で*
と**
が導入されましたが、当初は...
と併用されることを想定しておらず、すこしいびつな仕様になっていました。
...
は*, **, &
の構文糖であるというシンプルな仕様に修正しました。
ただ、この修正で...
が遅くなっているようなのでひょっとしたらrevertされるかもしれません。
_ nanasi [わからんでもないけどビミョー]