[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [bep] learning lisp: if, and, cond



n高橋です。

Reiko TAKAHASHI  (高橋玲子)writes:

> } 練習問題:
> } 前出の fact に変更をほどこし、引数が1以上の整数でなければ nil を返すよ
> } うにせよ。

>  考えてみました。

> (defun fact (n)
>   (if (and (integerp n) (>= n 1))
>     (if (= n 1)
>         1
>       (* n (fact (- n 1))))))

> でしょうか?

はい、いいようですね。もしかして初 defun ですか?  
だとすると、これで今日から lisp プログラマですね。

> (if ...)の中身について、条件式を評価した結果が t の場合どうするかのS式さ
> えあれば、nil の分のS式はべつになくてもいいんだ、ということが、教えていた
> だいていたはずなのに初め思いつけず、「うわっ、nil の分はどうしよう……」
> と最初少し悩みました。

上の書き方でいいんですが、もし明示的に nil を返したいのなら、そのまま
nil と書けばいいんですよ。もちろん nil もS式です。たとえば

(defun meaningless (arg) nil)

とすると、何を引数に与えても nil を返す無意味関数 meaningless が定義で
きます。

もし仮にR高橋版 fact を、明示的に nil 返すように書き変えるなら、

(defun fact(n)
  (if (and (integerp n) (>= n 1))
      (if (= n 1)
          1
        (* n (fact (- n 1))))
    nil))

ですね。

さて、実はここの and の中の integerp と >= の順序は非常に重要です。も
し >= を先にして (and (>= n 1) (integerp n)) としてしまうと、(fact 'x) 
とか (fact "abc") とかやったときエラーになってしまいます。>= の引数は
どちらも数値でなければならないからです。

and の一般形は (and 条件1 条件2 条件3 ...) という形をしていますが、
条件1, 条件2, ... を全部評価してからそれらが全部真かどうか調べるのでは
ありません。条件xを前から順番に評価して行き、どれか1つが nil となった
時点で残りの条件は無視して即座に nil を返します。ですから、

(setq x 5)
(and nil (setq x 6))

とした後でxの値を調べても5のままになっています。

>  上のLISPを*scratch*でC-jしてから、(fact ...) にいろいろな数を入れて遊ん
> でみました。

>  私のところでは、n の値が11を超えてしまうと、返ってくる値が負の数になっ
> たり、正の数でもへんな値になったりするみたいなのですが……(DOS版Emacsで
> す(^^;;;))。

Emacs Lisp が扱える整数の範囲には限りがあり、それを超えると変になりま
す。この範囲はプログラミング言語としては狭い方ですが、もっと大きな数が
使いたいようになる頃には、どうすればいいかもわかってくると思います。

> } もちろん if を重ねることもできます。なにしろ (if ...) もS式のひとつで
> } すから。なお、複雑な条件判定をするときには、if より便利な cond を使う
> } ことがよくあります。でも cond の紹介は上の練習問題ができた後ですね。

cond の一般形は次のような形をしています。

(cond
  (条件1 S式1の1 S式1の2 S式1の3 ...)
  (条件2 S式2の1 S式2の2 S式2の3 ...)
  (条件3 S式3の1 S式3の2 S式3の3 ...)
  ...
)

「条件x」を上から順に見て行き、評価結果が真になったら
「S式xの1」「S式xの2」「S式xの3」…を順番に評価して最後の「S式xの?」
の値を返します。
and の場合と同様、残りの「条件x+1」「条件x+2」…は調べさえしません。

では cond を使って fact を書き直してみましょう。

(defun fact (n)
  (cond
   ((not (integerp n)) nil)
   ((< n 1) nil)
   ((= n 1) 1)
   (t (* n (fact (1- n))))))

なんとなく想像つきますか?

「条件1」は (not (integerp n)) で、「S式1の1」は nil
意味は、もしnが整数でなかったら値は nil、です。

「条件2」は (< n 1) で、「S式2の1」はやっぱり nil
意味は、もしnが1よりも小さかったら値は nil、です。

「条件3」は (= n 1) で、「S式3の1」は1
意味は、もしnが1だったら値は1、です。

「条件4」は t で、「S式4の1」は (* n (fact (1- n)))
意味は、無条件で値は n かける (fact n引く1)、です。

いずれの場合も場合も「S式xの2」以降が存在しないので、「S式xの1」の値が
全体の値になります。

最後の「条件4」の t がちょっとわかりにくいかもしれません。上で書いたよ
うに、cond は条件xが真になったところでそれ以降の条件のチェックをとばし
ます。ということは、もし「条件4」のところまで来たのならば、

・nは整数でなくなく(つまり整数)、
・かつ1より小さくなく(つまり1以上)、
・しかも1でない、

ということになります。わかりやすく言うと、nは1よりも大きい整数だという
ことになります。nが1よりも大きな整数であれば、そのときの答はnの値にか
かわらず (* n (fact (1- n))) です。ですから「条件4」を t (つまり真)
としておいて、常に (* n (fact (1- n))) を返すようにしているわけです。

cond を書くときは以上のように、まず例外的な場合を取り出し、最後に「そ
れ以外なら」という意味で t という条件を置くのが普通です。もちろん Lisp
は nil 以外をすべて真ととらえますから、t の変わりに 1 とか 'a とか
"hello" とか書いてもいいんですが、わかりやすさのため t を使うのが習慣
です。

なお、cond の条件の最後が t でなく、かつすべての条件が nil となった場
合、cond 自身の値も nil となります。

-- 
TAKAHASHI Naoto
ntakahas@...
http://www.m17n.org/ntakahas/