可変長引数

書籍「プログラミングGauche」の中で高階関数の例として complement という関数を定義している。 これは述語を受取り、その述語が返す真偽値を逆転した述語を返すというものだ。

(define (complement pred)
  (lambda(x)(not (pred x))))

ところがこれには問題がある。 与える述語の arity が 1 の場合しか使えないということだ。
例えばこんな使い方をしようとすると失敗する。

((complement =) 1 2)

解決するのは簡単で、可変長引数を使えば良いだけのことだ。

(define (complement pred)
  (lambda x (not (apply pred x))))

もっと簡潔に書くことも出来る。

(define complement (pa$ compose not))

実際に Gauche でどのように定義しているかの見てみるとこんな感じだ。

(define (complement fn)
  (case (arity fn) ;; some optimization
    ((0) (lambda () (not (fn))))
    ((1) (lambda (x) (not (fn x))))
    ((2) (lambda (x y) (not (fn x y))))
    (else (lambda args (not (apply fn args))))))

基本的には可変長引数を使いつつも、よく有る場合 (arity が 2 個までの場合) を特殊なケースとして扱うことでリストのコピーを発生しないようにして効率化している。
さて、ここまではマエフリだ。 本題は Haskell である。
Haskell で complement を書くとどんなものかとやってみたのだ。

complement = (not .)

非常に簡単だ。
わかりやすくするために型も書いておこう。

complement :: (a -> Bool) -> (a -> Bool)

これにも最初に挙げた Scheme での例と同じ問題がある。 例として比較演算子を与えてみよう。

*Main> complement (==) 1 2

<interactive>:1:11:
    Couldn't match expected type `Bool'
           against inferred type `a -> Bool'
    In the first argument of `complement', namely `(==)'
    In the expression: complement (==) 1 2
    In the definition of `it': it = complement (==) 1 2

演算子==の型は

(==) :: (Eq a) => a -> a -> Bool

である。 右結合なので、わかりやすく括弧を補うとこうなるだろう。

(==) :: (Eq a) => a -> (a -> Bool)

つまり、 a -> Bool な型が必要なところに a -> (a -> Bool) を渡してしまったので型エラーになったわけだ。
Haskell で可変長引数を扱うのは非常にやっかいで、 Text.Pritf モジュールなんかは複雑なことになっているのは知っているが、今回定義しようとしている complement 程度の単純なものの場合は単純な解決がありそうな気がするのだが…。 それともやっぱり複雑な記述が必要だろうか?
Document ID: 3ed6e10d06023f1f533200ca3a3a76fb