Land of Lisp勉強ノート#4

Land of Lisp LISP
Land of Lisp

いよいよ第4章はプログラム言語らしくなってくる。

第4章 条件と判断

4.1 nilと()の対称性

Lispにおける単純な構文はその対称性に重要な役割を果たしている。

空とは偽なり

Lispでは空のリストを偽として扱う。if関数(fフォーム)の練習。

> (if '()          ;;リストは空であるのでfalse
    'i-am-true
    'i-am-false)
I-AM-FALSE

> (if '(1)         ;;リストは空でないのでtrue
    'i-am-true
    'i-am-false)
I-AM-TRUE

空リストを容易に検出できると、リストを再帰で使いやすくなる。
これはリストを扱うことを考えればよく分かる。
リストの長さを測ってみよう。

> (defun my-length (list)               ;;グローバル関数定義
    (if list                            ;;listの真偽を判定
         (1+ (my-length (cdr list)))    ;;真なら1カウントしてcdrを求めて再帰する
          0))                           ;;偽なら0を返す

> (my-length '(list with four symbols))
4

このように再帰はスマートであるが、効率の面で問題もあるとか。

()の四つの顔

大事。空リストは偽と評価されると言うだけでなく、空リストこそが唯一の偽値だ。
空リストと等価でない値は全て真の値として扱われる。
Lispにおける空リストの書き方は四通りある。

  • ‘()
  • ‘nil
  • ()
  • nil

これらの記述は全てLispが空リストであると定義している。文句は言わない。
条件分岐の際にはこれらを使うことになる。

4.2 条件分岐:ifとその仲間たち

ifは一度に一つずつ

  • ifの持つ2つの式のうち、どちらか一方だけが評価される
  • if文でできることは1つだけだ
> (if (= (+ 1 2) 3)
    'yup
    'nope)
YUP

> (if (= (+ 1 2) 4)
    'yup
    'nope)
NOPE

> (if '(1)
    'the-list-has-stuff-in-it
    'the-list-is-empty)
THE-LIST-HAS-STUFF-IN-IT

> (if '()
    'the-list-has-stuff-in-it
    'the-list-is-empty)
THE-LIST-IS-EMPTY

> (if (oddp 5)
    'odd-number
    'even-number)
ODD-NUMBER

> (if (oddp 5)    ;;これは大変だが、この関数は真の方にしか動かないから問題なし
    'odd-number
    (/ 1 0))
ODD-NUMBER

> (defvar *number-was-odd* nil)        ;;グローバル変数にnilを
>(if (oddp 5)
    (progn (setf *number-was-odd* t)    ;;変数にtrueを設定する処理を追加
            'odd-number)
    'even-number)
ODD-NUMBER
> *number-was-odd*
T

最後の例はちょっとめんどくさそう。
ifの条件式がtrueだから最初の式を評価する。ここにはprognと言うコマンドで変数への値設定処理が追加されている。その他は他のifと同じ。
設定された変数はnilからtに変更されている。

ifを超えて:whenとunless

prognを書かなくてもよい構文。暗黙のprognと呼ぶ。

> (defvar *number-is-odd* nil)

> (when (oddp 5)        ;;whenは条件が真のときにだけ囲まれた式を全て評価する
  (setf *number-is-odd* t)
   'odd-number)
ODD-NUMBER
> *number-is-odd*
T

> (unless (oddp 4)      ;;unlessは条件が偽のときにだけ囲まれた式を全て評価する
   (setf *number-is-odd* nil)
    'even-number)
EVEN-NUMBER
> *number-is-odd*
NIL

whenもunnlessも条件式が合わないときには何もしないでnilを返す。

万能条件コマンドcond

Lispにとって一番古い形式。Lispの石器時代からあったらしい。
これも暗黙のprognである。

> (defvar *arch-enemy* nil)
> (defun pudding-eater (person)
    (cond ((eq person 'henry) (setf *arch-enemy* 'stupid-lisp-alien)
              '(curse you lisp alien - you are my pudding))
          ((eq person 'johnny) (setf *arcj-enemy* 'useless-old-johnny)
              '(i hope you choked on my pudding johnny))
          (t  '(why you eat my pudding stranger ?))))

> (pudding-eater 'johnny)
(I HOPE YOU CHOKED ON MY PUDDING JOHNNY)
> *arch-enemy*
USELESS-OLD-JOHNNY
> (pudding-eater 'george-clooney)
(WHY YOU EAT MY PUDDING STRANGER ?)

かなり括弧で苦労しそうだが、被らない条件の切り分けが出来てる。
この場合は個人名だから、被っていない。

caseによる分岐

すべてeqで判断されるので、比較対象の値を並べることで分岐できる。

> (defun pudding-eater (person)
    (case person
        ((henry)  (setf *arch-enemy* 'stupid-lisp-alien)
                  '(course you lisp alien - you ate my pudding))
        ((johnny) (setf *arch-enemy* 'useless-old-johnny))
                  '(i hope you choked on my pudding johnny))
        (otherwise '(why you eat my pudding stranger ?))))

読みやすい。caseはシンボルの値で分岐する。
文字列の値で分岐することは出来ないので、要注意。

4.3 ちょっとした条件式のテクニック

andとorは単純な論理オペレータ。論理式を扱える。

隠された条件分岐、andとorを使う

> (and (oddp 5) (oddp 7) (oddp 9))    ;;全て奇数だからt
T
> (or (oddp 4) (oddp 7) (oddp 8))     ;;7が奇数だからt
T

> (defparameter *is-it-even* nil)
*IS-IT-EVEN*
> (or (oddp 4) (setf *is-it-even* t))  ;;最後まで評価されるので値は変化する
T
> *is-it-even*
T

> (defparameter *is-it-even* nil)
*IS-IT-EVEN*
> (or (oddp 5) (setf *is-it-even* t))  ;;結果が最初のリストで分かるので、値は変化しない
T
> *is-it-even*
NIL

このように論理式ではショートカットで処理される。応用すると下記。

(if *file-modified*                ;;ファイルに変更がある際は
    (if (ask-user-about-saving)    ;;ユーザーに意向を聞いてtrueなら
        (save-file)))              ;;ファイルを保存する

(and *file-modified* (ask-user-about-saving) (save-file))  ;;綺麗だ

(if (and *file-modified*           ;;中間的
         (ask-user-about-saving))
       (save-file))

真理以上のものを返す関数

Lispではnilとその同値なもの以外は全て真である。ということは真偽値以外の情報を真として返すこともできるということだ。

> (if (member 1 '(3 4 1 5))     ;;ifでmemberの返す値を判断して真となる
        'one-isinthe-list
        'one-is-not-in-the-list)
ONE-IS-IN-THE-LIST

> (member 1 '(3 4 1 5))        ;;member関数が返しているのは(1 5)なのだ
(1 5)

;ところでmemberが見つけた値を返すように仕様を変えると

> (if (member nil '(3 4 nil 5))     ;;ifはmemberの返す値をnilと判断して偽となる
        'nil-is-in-the-list
        'nil-is-not-in-the-list)
NIL-IS-NOT-IN-THE-LIST

> (member nil '(3 4 nil 5))        ;;member関数が返しているのはnilなのだ
nil
> (find-if #'oddp '(2 4 5 6))       ;;find-ifは最初の引数に別の関数を受け取る
5

> (if (find-if #'oddp '(2 4 5 6))   ;;find-ifは条件判断結果の真偽値を返す
        'there-is-an-odd-number
        'there-is-no-odd-number)
THERE-IS-AN-ODD-NUMBER
> (find-if #'null '(2 4 nil 6))     ;;この場合に注意 条件判断が逆になっている
NIL              ;対象性が崩れている

4.4 比較関数:eq、equal、そしてもっと

Lispでは比較に関するコマンドがたくさんあり、状況を判断して使うことが大事だ。

比較についてのコンラッドのルール

  1. シンボル同士は常にEQで比較すべし
  2. シンボル同士の比較でなければEQUALを使え

取り敢えずはeqとequalを覚えておけば良いかな。

> (defparameter *fruit* 'apple)
*FRUIT*

> (cond ((eq *fruit* 'apple) 'its-an-apple)      ;;シンボル同士は必ずeqを使う
        ((eq *fruit* 'orange) 'its-an-orange))
ITS-AN-APPLE
> (equql 'apple 'apple)        ;;シンボル同士の比較
T
> (equal (list 1 2 3) (list 1 2 3))   ;;リスト同士の比較
T
> (equal '(1 2 3) (cons 1 (cons 2 (cons 3 ()))))  ;;中身が同じだから
T
> (equal 5 5)            ;;整数同士の比較
T
> (equal 2.5 2.5)        ;;浮動小数点同士の比較
T
> (equal "foo" "foo")    ;;文字列同士の比較
T
> (equal #\a #\a)        ;;文字列同士の比較
T
> (eql 'foo 'foo)        ;;シンボル同士の比較
T
> (eql 3.4 3.4)          ;;数値同士の比較
T
> (eql #\a #\a)          ;;文字列同士の比較
T
> (equalp "Bob Smith" "bob smith")      ;;大文字小文字混在の文字列の比較
T
> (equalp 0 0.0)         ;;整数と浮動小数点数の比較
T
;;その他の比較コマンドは特定のデータ型に特化したもの

=                  ;;数値の比較
string-equal       ;;文字列の比較
char-equal         ;;文字の比較

4.5 本章で学んだこと

Lispの条件分岐について学んだ。この辺は場数が物を言うところ。

  • Lispでは式nil、’nil、()、'()は全て同じ
  • Lispではカラリストの判定が簡単なので、リストを頭から食べてゆく関数を簡素にかける
  • if等のLispの条件式は、条件に合致した式しか評価しない
  • 条件式で色んなことを一度にやりたければcondが使える
  • Lispでの比較はシンボルにはeq、ほかはequalを使うと覚えておけば良い

コメント

タイトルとURLをコピーしました