lat?をmapを使って書いてみた

「Scheme手習い」本文からは脱線するけど、関数 lat? の定義を見たとき、高階関数 map が使えるんじゃないかと思った。もちろん Gauche にも map は用意されている。
試しに書いてみたのがこれ。

(define all?
  (lambda (l)
    (cond
      ((null? l) #t)
      ((car l) (all? (cdr l)))
      (else #f))))

(define lat?
  (lambda (l)
    (all? (map atom? l))))

lat? を定義する前に補助関数として all? を定義している(探したけど見つからなかったので自分で定義した)。all? はリストを引数に取って、その要素すべてが真なら #t を返す。
さて、試してみよう。

gosh> (lat? '(bacon and eggs))
#t
gosh> (lat? '(bacon (and eggs)))
#f

うまくいったようだ。
ただ、この定義だと必ずリストの最後まで要素を見ていくのに対して、もとの再帰による定義だと場合によっては最後まで見る必要がないから、再帰のほうが効率はいいのかもしれない。

member? も map でできるかな?

lat?とmember?

lat?

関数 lat? は、引数にリストを取り、そのリストがラット(要素すべてがアトムであるか空であるリスト)のとき #t を返し、そうでないときには #f を返す。
「Scheme手習い」本文(p.16)では次のように定義されている(関数 atom? は定義済みとして)。

(define lat?
  (lambda (l)
    (cond
      ((null? l) #t)
      ((atom? (car l)) (lat? (cdr l)))
      (else #f))))

再帰が出てきた。関数 cond は複数の質問とその質問に対応する値をとり、結果として真となる質問に対応する値を返す。ここではふたつ目の質問 (atom? (car l)) に対応する値 (lat? (cdr l)) の部分が再帰になっている。
いくつか試してみよう。

gosh> (lat? '(Jack Sprat could eat no chicken fat))
#t
gosh> (lat? '((Jack) Sprat could eat no chicken fat))
#f
gosh> (lat? '(Jack (Sprat could) eat no chicken fat))
#f
gosh> (lat? '())
#t

ところで、このラットという用語は Scheme の用語なのか、この本の中でそう呼んでいるだけなのか、ググってみてもよくわからなかった。まあいいか。

member?

関数 member? はアトムとリストを引数に取り、アトムがリストのメンバーであるとき #t を返す。member? の定義は次のようになる(p.23)。

(define member?
  (lambda (a lat)
    (cond
      ((null? lat) #f)
      (else (or (eq? (car lat) a)
                (member? a (cdr lat)))))))

ここで誤植発見。本文では4行目が ((null? lat) nil) となっているけど、nil じゃなくて #f の間違いだろう。たぶん。
さて、新しい関数 or が出てきた。or は引数をふたつ取り、どちらかが真であれば #t を返す。このとき、ひとつ目の引数が真であれば、ふたつ目の引数は評価されない。
それから6行目ではまたも再帰が出てきている。それじゃ、いくつか試してみよう。

gosh> (member? 'tea '(coffee tea or milk))
#t
gosh> (member? 'poached '(fried eggs and scrambled eggs))
#f

関係ないけど poached eggs ってのは落とし卵のことのようだ。うちじゃよく味噌汁に入ってる。

再帰とnull?

lat? と member? に共通するのは、リストの要素をひとつずつ、(必要であれば)最後まで見て返す答えを決定することだ。cond のひとつ目の質問には両方の関数で (null? l) が使われている。これでリストの最後に到達したかどうかを判定している。

第1の戒律
(仮)いかなる関数を表現するときも最初の質問はすべて null? にすべし。