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されるかもしれません。
2022-12-25 (Sun) [長年日記]
_ ...
を*, **, &
の構文糖にすると言ったな、あれは嘘だ
Ruby Advent Calendar 2022の25日目の記事です。
昨日は羽角さんのirb.wasmの入力メソッドをSTDIOからRelineへ改修した話でした。
前回のあらすじ
前回の記事で、
Ruby 3.2で
*
と**
が導入されましたが、当初は...
と併用されることを想定しておらず、すこしいびつな仕様になっていました。...
は*, **, &
の構文糖であるというシンプルな仕様に修正しました。
と言っていました。ところが……
遅くなった?
松田さんから*, **
や...
による委譲が遅いというissueが報告されました。
forwardableを使った場合に比べて2倍以上遅いとのこと。forwardableが何でそんなに速いかというとruby2_keywordsを使っているからで、前回の記事でも書いたようにFeature #19134の修正前は
def foo(...)
bar(...)
end
は
ruby2_keywords def foo(*, &)
bar(*, &)
end
の構文糖でした。正直ruby2_keywordsという盲腸みたいな機能で速いのはずるいと思うんですが……。
修正案
実際のアプリケーションではそれほど影響はなさそうな気もしましたが、Railsでたくさん使われているらしいということで、修正案として、...
と*
/**
の併用を禁止するという提案をしました。
&
についても禁止してもいい気もしますが、Ruby 3.1で併用できていて互換性の問題があるということでこちらはそのまま。
ささださんによる最適化
リリースまで時間もないので上記の修正を行うつもりでまつもとさんの返事を待っていたところ、ささださんが最適化してみるのでちょっと待って、ということになり、休暇中のはずのささださんによってpull requestが作成されました。
f(*a, *kw)
の呼び出し規約を変えるという結構インパクトのある修正で、リリースまで12日しかなくてYJITチームの何名かはもうすぐ休暇に入ってしまうという話もあり、Ruby 3.2での導入は見送られました。
この最適化自体は、...
の仕様をどうするかに関わらず有用なので、おそらくRuby 3.3で取り込まれると思います。
修正
ということで、結局修正案のとおり、...
と*
/**
の併用を禁止するように修正しました。
修正中、以下の部分が最初抜けていてテストが通らずはまりましたが、元のなかださんのコードでは#define idFWD_KWREST 0
していたのでこの#ifdef
は不要だったという罠でした。
args_forward : tBDOT3
{
/*%%%*/
+#ifdef FORWARD_ARGS_WITH_RUBY2_KEYWORDS
+ $$ = 0;
+#else
$$ = idFWD_KWREST;
+#endif
/*% %*/
/*% ripper: args_forward! %*/
}
今回の修正でFORWARD_ARGS_WITH_RUBY2_KEYWORDS
が#define
されていなければruby2_keywordsを使わなくなったので、Ruby 3.2でささださんの最適化が入ったらそうするとよいように思います(が多分忘れる)。
まとめ
Ruby 3.2で...
を*, **, &
の構文糖にすると言っていましたが、そうはなりませんでした。
2022-12-29 (Thu) [長年日記]
_ T14 Gen 3のSSD換装・メモリ増設とUbuntuのインストール
Macbook Pro 2018のバッテリーが1時間くらいで切れるようになってしまって、注文していたThinkpad T14 Gen 3が届いたので、今回は久しぶりにLinuxを直接インストールすることにした。 とみたさんが同じマシンにLinuxを入れていたので大きな問題はないだろうという目論見である。
まずはその前にSSDの換装とメモリの増設を行った。
Thinkpadはケースを開けるのが楽そうだなと思っていたが、ネジを外すだけではだめだった。 XPS 13のSSDを換装した時に買っていたギターピックのような器具をヒンジ側からねじ込むとケースを外すことができた。 SSDはキオクシアの1TBのものに換装(純正はSK hynixだった)。
メモリは16GB増設。メモリもSSDもLenovo純正より格段に安い。
Ubuntuの22.04のインストールはとみたさんの記事を参考にSecure Bootを無効にしたら問題なかったが、「Windows Boot Managerとは別にインストール」でパーティション作成をインストーラまかせにしたら暗号化を選べなかったのでやり直すはめになった。 「それ以外」を選択して、/dev/nvme0n1p5をext4で/bootに、/dev/nvme0n1p6を暗号化用のパーティションにして/dev/mapper/nvme0n1p6_cryptを/に割り当てたら、ルートパーティションを暗号化してインストールできた。
voyager:~$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 3.2G 2.8M 3.2G 1% /run
/dev/mapper/nvme0n1p6_crypt 789G 38G 711G 6% /
tmpfs 16G 126M 16G 1% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
/dev/nvme0n1p5 1.9G 376M 1.4G 22% /boot
/dev/nvme0n1p1 256M 36M 221M 14% /boot/efi
tmpfs 3.2G 4.7M 3.2G 1% /run/user/1000
Windowsを消してインストールする場合は自分でパーティション作成しなくても暗号化できるのに、ちょっと不便である。
インストール直後は
- Bluetoothイヤホンだと音が出るのに内蔵スピーカーから音が出ない(デバイスは認識していた)
- 指紋認証が使えない
という問題があったが、apt install linux-image-oem-22.04b
でkernel 6.0.0-1009-oemを入れたら両方とも解決した。