case-lambda と let-optionals*

先日の記事ではプログラミング言語 Scheme における手続きの引数を省略できるようにする構文として case-lambdalet-optionals* を紹介した。 しかし、このふたつの構文は意味が異なる。 case-lambda は「手続きの数に応じて分岐する」ためのものであり、 let-optionals* は「省略された引数をデフォルト値で補う」ためのものだ。 引数の数に応じて分岐して不足する引数を補うことは出来るのだから case-lambda も引数の省略を実現する部品として見ることは出来るが、省略可能な引数をより直接的に表現しているのは let-optionals* だ。 私が let-optionals* を好んで使っているのはそういった理由からだ。

また、引数の省略を表現するために case-lambda を使おうとすると似たような表記の繰り返しがあり、冗長に思われる。 先日に例として出した iota を再度見てみよう。

(define iota
  (case-lambda
   ((count)       (iota count 0 1))
   ((count start) (iota count start 1))
   ((count start step)
    ;; 省略
    )))

iota と何度も書いているのが冗長に見えないだろうか。

もう少し引数の数が多い例を考えてみよう。 六個の引数があり、その内の五個が省略可能であるような手続き foo を書こうとするとこうなる。

(define foo
  (case-lambda
   ((a) (foo a 2))
   ((a b) (foo a b 3))
   ((a b c) (foo a b c 4))
   ((a b c d) (foo a b c d 5))
   ((a b c d e) (foo a b c d e 6))
   ((a b c d e f) (list a b c d e f))))
(define (foo a . rest-args)
  (let-optionals* rest-args
      ((b 2)
       (c 3)
       (d 4)
       (e 5)
       (f 6))
    (list a b c d e f)))

case-lambda を使った場合に同じことを何度も書いているのはいかにも不格好なのが目立つ。

繰り返すが、 case-lambdalet-optionals* は意味が異なり、引数の省略をより直接的に表現しているのは let-optionals* だ。 しかし、省略以外の理由で引数の数によって挙動を変える手続きというものはそう多くはない。 つまり、 case-lambda が (let-optionals* よりも) 妥当な場合というのは少ないのではないだろうか。 強いて言うならば、割り算をする手続きである / や、引き算の手続きである - が引数一個で呼ばれたときに変則的な振舞いがあるというのが思い付くくらいだ。 そういった変則的な挙動は基本的には悪い設計なので let-optionals* よりも case-lambda が使いたくなる状況があったとしたらそれは悪いサインである可能性がある。

そんなわけで、私は case-lambda の有用性について懐疑的だ。

Document ID: c894eeb80710ca84a1dabc448f3e2952