Forum: Ruby-dev Add option hash support to rb_scan_args()

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
74f896b312b786ee75a18073941e2457?d=identicon&s=25 Akinori MUSHA (Guest)
on 2009-02-18 20:34
(Received via mailing list)
 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) のように展開させる形になれば
その辺はすっきりすると思います。
0ec4920185b657a03edf01fff96b4e9b?d=identicon&s=25 Yukihiro Matsumoto (Guest)
on 2009-02-19 16:11
(Received via mailing list)
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"
<knu@iDaemons.org> writes:

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

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

$B3N$+$K$J$s$i$+$NJ}K!$G(Brb_scan_args$B$K$h$k%-!<%o!<%I0z?t$N;Y1g(B
$B$OM_$7$$$G$9$h$M$(!#(B
74f896b312b786ee75a18073941e2457?d=identicon&s=25 Akinori MUSHA (Guest)
on 2009-02-20 06:30
(Received via mailing list)
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)と
ともに導入されるべきだと思います。
F24ff61beb80aa5f13371aa22a35619c?d=identicon&s=25 Yusuke ENDOH (Guest)
on 2009-02-20 14:33
(Received via mailing list)
$B1sF#$G$9!#(B

2009/02/20 14:27 Akinori MUSHA <knu@idaemons.org>:
>> [ruby-dev:35379]$B$GCfED$5$s$,%-!<%o!<%IBP1~$N%Q%C%A$r%]%9%H$7(B
>> $B$F$/$@$5$C$F$$$^$9!#%O%C%7%e$r<h$j=P$9$N$G$O$J$/!"%-!<%o!<%I(B
>> $B$rD>@\;XDj$9$k$N$,%-!<%o!<%I0z?t$i$7$$$+$b$7$l$^$;$s!#(B
(snip)
> $B%-!<%o!<%I0z?t$NK\3JE*$J%5%]!<%H$O!"?7$7$$J8K!$dOHAH$_(B(API)$B$H(B
> $B$H$b$KF3F~$5$l$k$Y$-$@$H;W$$$^$9!#(B


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

$BNc$($P!"$3$s$J46$8$K=q$1$k$H$+!#(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"]
0ec4920185b657a03edf01fff96b4e9b?d=identicon&s=25 Yukihiro Matsumoto (Guest)
on 2009-02-21 16:37
(Received via mailing list)
$B$^$D$b$H(B $B$f$-$R$m$G$9(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 <mame@tsg.ne.jp>
writes:

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

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

|$BNc$($P!"$3$s$J46$8$K=q$1$k$H$+!#(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"]

$B$H$$$&Iw$K=q$1$k$h$&$K$7$?$$$H;W$C$F$$$^$9!#(B
74f896b312b786ee75a18073941e2457?d=identicon&s=25 Akinori MUSHA (Guest)
on 2009-02-23 05:08
(Received via mailing list)
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)))
This topic is locked and can not be replied to.