#:g1: Common Lispで多重な可変長引数

Posted 2021-06-19 18:16:27 GMT

InfoQ: Swift 5.4が複数の可変数引数、リザルトビルダなどをサポートの記事を読んで、lSwiftがCommon Lispでいう可変長なキーワード引数をサポートするようなのですが、Common Lispだとどんなことになるか真似してみることにしました。

その1: キーワード引数が可変長引数化するらしい

;// The third parameter does not require a label because the second isn't variadic.
;func splitVarargs(a: Int..., b: Int, _ c: Int...) { } 
;splitVarargs(a: 1, 2, 3, b: 4, 5, 6, 7)
;// a is [1, 2, 3], b is 4, c is [5, 6, 7].

一見して混乱の元に感じるのですが、Swiftだと便利な状況なのでしょう。
Common Lispだとキーワード引数は可変にすることはできません。

ただ下記のようにマクロのラムダ引数であればリストにできるので機能的に同等のものは書けるでしょう。

(defmacro split-varargs (&key ((:a (&rest a) nil)) b ((:c (&rest c) nil)))
  `(list ,@a ,b ,@c))

(split-varargs :a (1 2 3) :b 4 :c (5 6 7))(1 2 3 4 5 6 7)

(split-varargs :b 4 :a (1 2 3) :c (5 6 7))(1 2 3 4 5 6 7)

;splitVarargs(b: 4)
;// a is [], b is 4, c is [].

(split-varargs :b 4)(4)

twoVarargs()

(two-varargs) → nil

寧ろ括弧でグルーピングした方が読み易いのでは

その2: 固定長と可変長のキーワード引数を混在できる

固定長の場合、ラベル(キーワード)を省略できるそうなのですが、混乱しそうな機能をどんどん盛り込んでる気がしてならない……。

;// The third parameter does not require a label because the second isn't variadic.
;func splitVarargs(a: Int..., b: Int, _ c: Int...) { } 
;splitVarargs(a: 1, 2, 3, b: 4, 5, 6, 7)
;// a is [1, 2, 3], b is 4, c is [5, 6, 7].

;splitVarargs(a: 1, 2, 3, b: 4, 5, 6, 7)

(defmacro split-varargs (&key ((:a (&rest a) nil)) b ((:c (&rest c) nil)))
  `(list ,@a ,b ,@c))

(split-varargs :a (1 2 3) :b 4 :c (5 6 7))(1 2 3 4 5 6 7)

(split-varargs :b 4 :a (1 2 3) :c (5 6 7))(1 2 3 4 5 6 7)

;splitVarargs(b: 4) ;// a is [], b is 4, c is []. (split-varargs :b 4)(4)

固定長の方にはデフォルト値を与えることが可能

;// Note the third parameter doesn't need a label even though the second has a default expression. This
;// is consistent with the current behavior, which allows a variadic parameter followed by a labeled,
;// defaulted parameter, followed by an unlabeled required parameter.
;func varargsSplitByDefaultedParam(_ a: Int..., b: Int = 42, _ c: Int...) { } 

;func varargsSplitByDefaultedParam(_ a: Int..., b: Int = 42, _ c: Int...) { } 

(defmacro varargs-split-by-defaulted-param (&key ((:a (&rest a) nil)) (b 42) ((:c (&rest c) nil)))
  `(list ,@a ,b ,@c))

;varargsSplitByDefaultedParam(1, 2, 3, b: 4, 5, 6, 7) ;// a is [1, 2, 3], b is 4, c is [5, 6, 7].

(varargs-split-by-defaulted-param :a (1 2 3) :b 4 :c (5 6 7))(1 2 3 4 5 6 7)

;varargsSplitByDefaultedParam(b: 4, 5, 6, 7) ;// a is [], b is 4, c is [5, 6, 7].

(varargs-split-by-defaulted-param :b 4 :c (5 6 7))(4 5 6 7)

;varargsSplitByDefaultedParam(1, 2, 3) (varargs-split-by-defaulted-param :a (1 2 3))(1 2 3 42)

Common Lispの標準のラムダ引数では真似できないところ

自明なところで、ラベルを省略できる

;twoVarargs(1, 2, 3)

NG (two-varargs 1 2 3)

上記の場合、(two-varargs :a 1 2 3)(two-varargs 1 2 3)と書けたりするということなのですが、Common Lispの場合、引数をリストで受け取って全部自前で処理すれば可能ですが、組み込みの機能では無理です。
しかし、これも混乱の元になる機能のような……。

まとめ

キーワード引数で&restというのはCommon Lispでも実際に使っているのを目にしたことはありませんが、多分、見掛けが関数呼び出しに見えてしまうので避けられるのでしょう。


HTML generated by 3bmd in LispWorks 7.0.0

comments powered by Disqus