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? にすべし。

eq?

関数 eq? は2つの引数を取り、同じものならば真(#t)を返す。

gosh> (eq? 'Harry 'Harry)
#t
gosh> (eq? 'margarine 'butter)
#f

本文には、2つの引数について「両方とも数でないアトムです。」とあるけど、脚注には「実際、eq? の引数にリストがきてもかまいません。」、「実際、eq? の引数に数がきてもかまいません。」とある。なんか、こういう例外というか本文と違うのが多いな。何なんだろ。

gosh> (eq? '(a b c) '(a b c))
#f

ふむ、引数がリストの場合は、同じリストでも偽(#f)になるな。
数ではどうだろう。

gosh> (eq? 7 7)
#t
gosh> (eq? 1 4)
#f

引数が数の場合は、同じ数なら真(#t)になった。

Eq? の掟
関数 eq? は2つの引数を取る。
どちらも数でないアトムでなければならない。

atom?

関数 atom? は任意のS式を引数に取り、それがアトムであるとき真(#t)を返す。といっても Gauche に atom? は用意されていないので、まずは脚注にある定義を写経。

gosh> (define atom?
  (lambda (x)
    (and (not (pair? x)) (not (null? x)))))
atom?

関数の定義の仕方は、本文で触れていないのでここでも触れない。そのうち出てくるだろう。

さあ、試してみよう。

gosh> (atom? 'Harry)
#t
gosh> (atom? '(Harry had a heap of apples))
#f
gosh> (atom? (car '(Harry had a heap of apples)))
#t
gosh> (atom? (cdr '(Harry had a heap of apples)))
#f
gosh> (atom? (cdr '(Harry)))
#f

最後の例では、(cdr '(Harry)) が 空リストなので偽(#f)なのだな。

null?

関数 null? は引数が空リストのとき真(#t)を返す。

gosh> (null? '())
#t
gosh> (null? (quote ()))
#t
gosh> (null? '(a b c))
#f

(quote ())'() の別の書き方。最後の例では、引数が空リストでないので偽(#f)が返ってきている。

本文には「アトムの null? をたずねることはできません」とあるけど、脚注には「実際、(null? α) は空リスト以外はすべて偽になります。」とある。

gosh> (null? 'spaghetti)
#f

Null? の掟
関数 null? はリストに対してのみ定義される。

cons

関数 cons は任意のS式をリストの先頭に付け加える。

gosh> (cons 'peanut '(butter and jelly))
(peanut butter and jelly)
gosh> (cons '(banana and) '(peanut butter and jelly))
((banana and) peanut butter and jelly)

cons は2つの引数をとり、ひとつ目は任意のS式、ふたつ目はリストでなければならない。

gosh> (cons 'a 'b)
(a . b)

あれ、エラーにならないな。リストともちょっと違う。なんだかわからないけど、そのうち出てくるだろう。それまでは保留。
脚注には、「実際、(cons α β) はすべての値αとβにうまく働いて、(car (cons α β))=α、(cdr (cons α β))=β となります。」とある。

gosh> (car (cons 'a 'b))
a
gosh> (cdr (cons 'a 'b))
b

Cons の掟
関数 cons は2つの引数を取る。
cons の第2引数はリストでなければならない。
結果はリストとなる。

carとcdr

car

関数 car はリストの中の最初のS式を返す。

gosh> (car '(a b c))
a
gosh> (car '((a b c) x y z))
(a b c)

アトムや空リストに適用するとエラーになる。

gosh> (car 'atom)
*** ERROR: pair required, but got atom
Stack Trace:
_______________________________________
gosh> (car '())
*** ERROR: pair required, but got ()
Stack Trace:
_______________________________________

Car の掟
関数 car は空でないリストに対してのみ定義される。

cdr

関数 cdr (クダーと読む)はリストの中から最初のS式を取り除いた、残りのリストを返す。

gosh> (cdr '(a b c))
(b c)
gosh> (cdr '((a b c) x y z))
(x y z)
gosh> (cdr '(hamburger))
()

S式がひとつしかないリストの場合には、空リストが返ってくるんだな。

アトムや空リストに適用すると、エラーになる。

gosh> (cdr 'atom)
*** ERROR: pair required, but got atom
Stack Trace:
_______________________________________
gosh> (cdr '())
*** ERROR: pair required, but got ()
Stack Trace:
_______________________________________

Cdr の掟
関数 cdr は空でないリストについてのみ定義される。
空でないリストの cdr は常に別のリストになる。

アトムとリストとS式

Scheme ではすべてはS式だ。基本的なデータも、リストも、関数も全部、S式。
アトムもS式。アトムというのは、Scheme での最小単位のようなものらしい。括弧ではない文字からなる文字列(データとしての文字列とは違うことに注意)や数字の列がアトム。ちょっと確かめてみよう。

gosh> atom
*** ERROR: unbound variable: atom
Stack Trace:
_______________________________________

あれ、エラーになった。そうか、クォートしてやらないといけない。クォートすると文字の列をそのものとして扱ってくれる。

gosh> 'atom
atom
gosh> 12345
12345
gosh> '*abc$
*abc$

リストは、S式を括弧でくくったもの。リスト自体もS式なので、リストのリストも作ることができる。これも当然S式。

gosh> '(x y z)
(x y z)
gosh> '((x y) z)
((x y) z)
gosh> '()
()

最後のは空のリスト。これもS式。

Schemeをはじめようと思う

巳年も終わったから Python も終わり、というわけでは決してないのだけど、新しい年には新しい言語を、ということで以前ちょっとだけ触ったことのある Scheme をはじめようと思う。
Windows で動作する Scheme 処理系はWindows 野郎のための Scheme 処理系 ― 年金ロボットをめざしてでいくつか紹介されているけど、あえて紹介からはずされている Gauche を使うことにする。理由は簡単、以前触った Scheme というのが Gauche だからだ。

まずは Gauche のインストールから。↓このページから Windows用バイナリインストーラをダウンロードした。バージョンは0.9.3.3。
 cf. http://practical-scheme.net/gauche/download-j.html
msi形式のインストーラなので、ダブルクリックして起動すればあとはすんなりとインストールできた。

コマンドプロンプトを起動して、goshと入力するとインタープリタがインタラクティブモードで起動する。

^o^ > gosh
gosh>

ここでいろいろ試してみればいいわけだな。

参考書には有名な「Scheme手習い」を使う。これまたずいぶん前に買ったきりほとんど読んでない本で、奥付には平成22年10月25日第1版第1刷発行とある。
[amazonjs asin=”4274068269″ locale=”JP” title=”Scheme手習い”]

それから、Gauche のオンラインマニュアルのページはここ。
 cf. Gauche ユーザリファレンス

さあ、はじめよう。