Add option hash support to rb_scan_args()

 rb_scan_args() にoption hash対応を組み込むのはどうでしょうか。
実装してみたのでパッチを添付しました。

argc = rb_scan_args(argc, argv, ":12", &opt, &path, &mode, &perm);

ã®ã‚ˆã†ã«é ­ã« : ã¨æ›¸ãã¨ã€å¿…é ˆå¼•æ•°ã‚ˆã‚Šå¤šãã®å¼•æ•°ãŒä¸Žãˆã‚‰ã‚Œã€ãã®
最後の引数がHash(ないし #to_hash で変換可能)ãªå ´åˆã«å–å¾—ã•ã‚Œã¾ã™ã€‚

 なお、Rubyでは

def open(path, mode = File::RDONLY, perm = 0666, opt = nil)

のように書くことが多いですが、これに依存して option hash を与え
ãªã„å ´åˆã«(空のHashでなく) nil を渡す習慣が存在しています。この
ときに読み捨てられた nil があったかどうかを判定できるようにする
ため、返り値は元の argc そのままではなく、 option hash (nil の
å ´åˆã‚’å«ã‚€)を除いた数を返すようにしてみました。

 : ã‚’æŒ‡å®šã™ã‚‹å ´åˆã¯ä¸Šè¨˜ã®ã‚ˆã†ã« argc を更新するのが定石となり
ます。(nil を読み捨てる条件は README.EXT* に記載しました)

 ある種 option hash は特別なので妥当な仕様だと考えていますが、
いかがでしょうか。いずれは、キーワード引数も &block 同様の疑似
引数となり、 open(path, **hash) のように展開させる形になれば
その辺はすっきりすると思います。

In message “Re: [ruby-dev:38048] Add option hash support to
rb_scan_args()”
on Thu, 19 Feb 2009 04:33:12 +0900, “Akinori MUSHA”
[email protected] writes:

|e$B!!e(Brb_scan_args() e$B$Ke(Boption hashe$BBP1~$rAH$9~$`$N$O$I$&$G$7$g$&$+!#e(B
|e$B<BAu$7$F$
$?$N$G%Q%C%A$rE:IU$7$^$7$?!#e(B
|
| argc = rb_scan_args(argc, argv, “:12”, &opt, &path, &mode, &perm);
|
|e$B$N$h$&$KF,$Ke(B : e$B$H=q$/$H!“I,?\0z?t$h$jB?$/$N0z?t$,M?$($i$l!”$=$Ne(B
|e$B:G8e$N0z?t$,e(BHash(e$B$J$$$7e(B #to_hash e$B$GJQ492DG=e(B)e$B$J>l9g$K<hF@$5$l$^$9!#e(B

[ruby-dev:35379]e$B$GCfED$5$s$,%-!<%o!<%IBP1~$N%Q%C%A$r%]%9%H$7e(B
e$B$F$/$@$5$C$F$$$^$9!#%O%C%7%e$r<h$j=P$9$N$G$O$J$/!"%-!<%o!<%Ie(B
e$B$rD>@;XDj$9$k$N$,%-!<%o!<%I0z?t$i$7$$$+$b$7$l$^$;$s!#e(B

e$B3N$+$K$J$s$i$+$NJ}K!$Ge(Brb_scan_argse$B$K$h$k%-!<%o!<%I0z?t$N;Y1ge(B
e$B$OM_$7$$$G$9$h$M$(!#e(B

At Fri, 20 Feb 2009 00:10:11 +0900,
matz wrote:

[ruby-dev:35379]で中田さんがキーワード対応のパッチをポストし
てくださっています。ハッシュを取り出すのではなく、キーワード
を直接指定するのがキーワード引数らしいかもしれません。

 見てみました。仕様案としては十分あると思いますが、その実装では
ãŸã¶ã‚“ã†ã¾ãè¡Œãã¾ã›ã‚“ã€‚å¿…é ˆå¼•æ•°ã®ãƒã‚§ãƒƒã‚¯ã‚’è¡Œã†å‰ã«æœ€å¾Œã®å¼•æ•°ãŒ
ハッシュなら無条件に取ってしまっているので、オプションハッシュの
つもりではなく渡したハッシュがそう受け取られて除去されてしまい、
引数が足りないとエラーにされてしまうケースが出ると思います。

 それは私も実際に既存のオプションハッシュを取るメソッドに適用
してみて気づいたことで、私のパッチではまず初めにpostargを含めた
å¿…é ˆå¼•æ•°ã®æ•°ã‚’è¨ˆç®—ã—ã¦ã‹ã‚‰ã€ã‚ªãƒ—ã‚·ãƒ§ãƒ³ãƒãƒƒã‚·ãƒ¥ã¨è€ƒãˆã¦ã‚ˆã„ã‹ã‚’
判断するようにしています。

 また、取得したオプションハッシュを(Hash の形で)下請け関数に
投げる例も多く、各キーワードに対応した値の取り出しまでできても、
現状の実装に適用するには改修範囲が大きくなってしまいます。

 ということで実際に使うような改修も含めて実装し、テストを行った
ものとして検討していただけるとうれしいです。

確かになんらかの方法でrb_scan_argsによるキーワード引数の支援
は欲しいですよねえ。

 先のメールでも少し出ましたが、将来的にはメソッド定義の段階
(rb_define_method)でシグネチャを指定するようにすべきでしょうね。
そうすれば、現在各メソッド内で行っている基本的な引数チェックの
手間が省けるほか、その際のエラーメッセージや Method#parameters
などのリフレクションAPIの返り値をCで定義したメソッドについても
Rubyで定義したメソッドと等しくinformativeかつhuman readableな
ものにできます。

 rb_scan_args はいずれ消えゆくものと考えて、現状の実装が少し
楽になる程度の改修に留めるのが落としどころではないでしょうか。
ã‚­ãƒ¼ãƒ¯ãƒ¼ãƒ‰å¼•æ•°ã®æœ¬æ ¼çš„ãªã‚µãƒãƒ¼ãƒˆã¯ã€æ–°ã—ã„æ–‡æ³•ã‚„æž çµ„ã¿(API)と
ともに導入されるべきだと思います。

e$B1sF#$G$9!#e(B

2009/02/20 14:27 Akinori MUSHA [email protected]:

[ruby-dev:35379]e$B$GCfED$5$s$,%-!<%o!<%IBP1~$N%Q%C%A$r%]%9%H$7e(B
e$B$F$/$@$5$C$F$$$^$9!#%O%C%7%e$r<h$j=P$9$N$G$O$J$/!“%-!<%o!<%Ie(B
e$B$rD>@;XDj$9$k$N$,%-!<%o!<%I0z?t$i$7$$$+$b$7$l$^$;$s!#e(B
(snip)
e$B%-!<%o!<%I0z?t$NK\3JE*$J%5%]!<%H$O!”?7$7$$J8K!$dOHAH$_e(B(API)e$B$He(B
e$B$H$b$KF3F~$5$l$k$Y$-$@$H;W$$$^$9!#e(B

e$B%-!<%o!<%I0z?t$i$7$$0z?t$N<u$1<h$jJ}$H$$$&$H!“e(BC API e$B$h$je(B
Ruby e$B%l%Y%k$NJ}$,5$$K$J$j$^$9!#e(B
e$B6a$$>-Mh!”?7$7$$J8K!$rF3F~$9$kM=Dj$d9=A[$O$"$k$N$G$7$g$&$+!#e(B

e$BNc$($P!"$3$s$J46$8$K=q$1$k$H$+!#e(B

def foo(x, y, z, a: “a”, b: “b”, c: “c”)
p [x, y, z, a, b, c]
end

foo(1, 2, 3, c: “X”) #=> [1, 2, 3, “a”, “b”, “X”]

e$B$^$D$b$He(B e$B$f$-$R$m$G$9e(B

In message “Re: [ruby-dev:38057] Re: Add option hash support to
rb_scan_args()”
on Fri, 20 Feb 2009 22:31:25 +0900, Yusuke ENDOH [email protected]
writes:

|e$B%-!<%o!<%I0z?t$i$7$$0z?t$N<u$1<h$jJ}$H$$$&$H!“e(BC API e$B$h$je(B
|Ruby e$B%l%Y%k$NJ}$,5$$K$J$j$^$9!#e(B
|e$B6a$$>-Mh!”?7$7$$J8K!$rF3F~$9$kM=Dj$d9=A[$O$"$k$N$G$7$g$&$+!#e(B

e$BM=Dj$d9=A[$O$“$j$^$9!#$,!”;~4|$O$J$s$H$b!#e(B2.0e$B$K$J$j$=$&$G$9e(B
e$B$7!"$=$N;~4|$O40A4$KL$Dj$G$9!#e(B

|e$BNc$($P!"$3$s$J46$8$K=q$1$k$H$+!#e(B
|
| def foo(x, y, z, a: “a”, b: “b”, c: “c”)
| p [x, y, z, a, b, c]
| end
|
| foo(1, 2, 3, c: “X”) #=> [1, 2, 3, “a”, “b”, “X”]

e$B$H$$$&Iw$K=q$1$k$h$&$K$7$?$$$H;W$C$F$$$^$9!#e(B

At Sun, 22 Feb 2009 00:36:27 +0900,
matz wrote:

予定や構想はあります。が、時期はなんとも。2.0になりそうです
し、その時期は完全に未定です。

[ruby-dev:23533] に提案がありますね。構文もさりながら、C APIの
設計もなかなか大変そうです。argc/argvで表せないと多くのAPIを直す
必要が出てくるとか、受け渡しはfirst classなHashでいいのかとか、
引数解析のパフォーマンスはどうかとか。

|例えば、こんな感じに書けるとか。
|
| def foo(x, y, z, a: “a”, b: “b”, c: “c”)
| p [x, y, z, a, b, c]
| end
|
| foo(1, 2, 3, c: “X”) #=> [1, 2, 3, “a”, “b”, “X”]

という風に書けるようにしたいと思っています。

ã€€é †åºãªã—åå‰å¼•æ•°ã¨ã„ã†ã®ã‚‚ã‚ã£ãŸæ–¹ãŒã„ã„ã‚“ã˜ã‚ƒãªã„ã§ã—ã‚‡ã†ã‹ã€‚
ã§ãªã„ã¨ã€é †åºã«ä¾å­˜ã—ãŸç”¨æ³•ãŒåºƒã¾ã£ã¦å¼•æ•°ã‚’å»ƒæ­¢ã™ã‚‹ã“ã¨ãŒé›£ã—ã
なりそうです。

 あとは、

  • å¿…é ˆå¼•æ•°ã«ã‚‚åå‰ã‚’ä»˜ã‘ãŸã„

  • 実装上仮引数名は短くしたいがAPIとしての名前(キーワード)は長く
    わかりやすくしたい

ã¨ã„ã†è¦æ±‚ã‚‚è€ƒæ…®ã—ã€åå‰ã«åŠ ãˆã¦ä»®å¼•æ•°åã‚‚æŒ‡å®šã§ãã‚‹ã‚ˆã†ã«è€ƒãˆã¦
みたのが以下の構文です。

Ruby

def meth(mand1: var1, # leading mandatory argument(s)
opt1: var2=val, # ordered optional argument(s)
*varlen: var3, # variable length arguments
mand2: var4 # ordered mandatory argument(s)
opt2:: var5=val # unordered argument(s)
**var6, # all arguments in hash
&block)

end

The var1, var2, var3, var4, var5 parts (formal parameter names)

above are omittable.

methodname(x, y) # := meth(mand1: x, mand2: y)
methodname(x, z, y) # := meth(mand1: x, opt1: z, mand2: y)
methodname(x, y, varlen: l) # := meth(mand1: x, varlen: l, mand2: y)
methodname(x, y, :varlen => l) # ditto

#methodname(x, y, h) # not allowed anymore
methodname(x, y, **h) # but hash splat is instead

a = [x, y]
h = { opt1: m, opt2: n }
methodname(*a, **h) # := meth(mand1: x, opt1: m, mand2: y,
opt2: n)

h1 = { mand1: x, mand2: y, opt1: r }
h2 = { opt1: p, opt2: q }
methodname(**h1, h2) # := meth((h1.merge(h2)))