孤独にそっくり

開いている窓の前で立ち止まるな

Land of Lisp3章~9章まで復習した

http://www.flickr.com/photos/12516383@N07/8517658785
photo by whileimautomaton
9章まで読んですでに頭からすっぽ抜けてることが多かったんでサラッと復習しました。

全部書いても仕方ないんで、僕的に忘れやすいところや、難しいところをメモとしてまとめています。順番も章ごとではありません。

条件式

  • ifの基本的な使い方
(if (条件式)
        真
        偽)

if文でできることは一つだけだが、prognを使うと一つの式の中に余分なコマンドを押しこむことが出来る。
または、暗黙のprognであるwhenとunlessを使えばいい。

  • when : 条件が真の時に囲まれた式を全て実行
  • unless : 条件が偽の時に囲まれた式を全て実行
(when (条件式)
        (実行したい式)
  • cond 万能

様々な関数

返り値に真偽以外も返す関数

  • member : リスト中にある要素が含まれているか調べる。真偽以外にも、その値を銭湯に持つ部分のリストを返す
(member 1 '(3 4 1 5))
>(1 5)
(if (member 1 '(1 2 3 ))
	'gorira
	'goriragorira)
>'gorira
  • find-if : 制約を満たすものを取り出す、真偽を返す
(find-if #'関数 'リスト)

(find-if #'oddp '(2 4 5 6)
>5
(if(find-if #'oddp '(2 4 5 6))
        'there-is-an-odd-number
        'there-is-no-odd-number)
>there-is-an-odd-number

ここで出てきた#'はファンクションオペレータと呼ばれるもの。

#'関数 = (function 関数)

ごった煮

  • assoc : キーを与えるとリストを呼び出す
  • find : リストから与えられた要素を探す関数。findは関数の後ろに特別な引数を渡すことが出来る。
(find 'y '((5 x)(3 y)(7 z)) :key #'cadr)

yをcadrに持つような最初の要素をリストから探し出す。それぞれ、

  1. 「:」キーワードシンボル。常に自分自身を指すシンボル
  1. 値そのもの。ここではcadr関数
  • remove-if-not : 渡されたリストの各要素に第一引数の関数を適用し、それが真の値を返さなかったものを除いたリストを得る?補足で

Common Lisp 入門読んだら分かりやすい例があった。

(remove-if #'oddp '(10 11 12 13 14 15 16 17 18 19) ) => (10 12 14 16 18)
(remove-if-not #'oddp '(10 11 12 13 14 15 16 17 18 19) ) => (11 13 15 17 19)

ちょっとややこしい

  • coerce : 文字列を文字のリストへ変換
  • substitute-if 与えられたテスト関数の結果によって値を置き換える関数。様々なデータ型のオブジェクトを引数としてとり、それぞれに適切な処理を行うジェネリックな関数
(substitute-if 置き換える文字 #'条件であるテスト関数 '(リスト))

高階関数

  • mapcar : 引数に他の関数とリストを受け取って、リストの要素一つ一つをその関数に引き渡す。結果は新しいリストにして返す。
(mapcar #'sqrt '(1 2 3 4))
>(1 1.141423 1.7320508 2 2.236068)
  • mapc : mapcarとは違って、リストを返さない。副作用だけ使う。
  • mapcan : mapcarの仲間 mapcanに渡す関数はリストを返さないといけない 返されたリストを全てappendして返す。
(defun ingredients (order)
 (mapcan (lambda (burger)
		(case burger
		 (single (list 'patty))
		 (double (list 'patty 'patty))
		 (double-cheese (list 'patty 'patty 'cheese))))
	 order))
>(PATTY PATTY PATTY CHEESE PATTY PATTY)
;mapcarじゃダメなの?

(defun ingredients (order)
 (mapcar (lambda (burger)
                (case burger
                 (single (list 'patty))
                 (double (list 'patty 'patty))
                 (double-cheese (list 'patty 'patty 'cheese))))
         order))
>((PATTY) (PATTY PATTY CHEESE) (PATTY PATTY))
;mapcarだと、リストで返すから、リストのリストを返しちゃう
  • apply : 渡されたリスト中の要素一つ一つを別個の引数としてターゲットの関数に渡す。つまり、引数を関数に渡したいけど、リストになっているときに分解してくれるから便利。本だけだとわからなかったので、補足として

Common Lisp 入門を見た。

(apply #'関数 'リスト)

(mapcar #'length '("abc" "defg" "hijkl" "mnopqr")) => (3 4 5 6)
;リストになっちゃうか>ら引数にできない
(apply #'+ (mapcar #'length '("abc" "defg" "hijkl" "mnopqr"))) => 18
;分解してくれるから出来る

ローカル変数・関数

  • let : ローカル変数を定義する
(let (変数定義)
 本体)

(let ((a 5) (b 4))
        (+ a b))
>11
  • let* : letでは同時に定義される変数が参照できないけど、let*はできる
(let* ((a 5)
	(b (+ a 2)))
	b)
>7
  • flet : ローカル関数を定義する。最初の2行で関数を宣言。flet内で使える。複数でも可能
(flet ((関数名(引数)
        関数本体))
        本体)

(flet ((f(n) ;関数名
         (+ n 10))) ;関数本体
        ((g(n) ;関数名
         (- n 3))) ;関数本体
        (g(f 5))) ;本体
>12
  • labels : fletとは違って、ローカル関数の中でも同じスコープで定義されるローカル関数が使える。
(labels ((a(n) ;関数名
         (+ n 5)) ;関数本体
        (b(n)  ;関数名
        (+ (a n) 6))) ;関数本体(ただし関数aを使っている)
        (b 10)) ;本体
>21

無名関数lambda

例えば数を半分にする関数を書く

(defun half(n)
	(/ n 2))

これをmapcarするときは、halfをわざわざ呼ばないといけない

(mapcar #'half '(2 4 6 8 10))

lambdaなら、スッキリ書ける

(mapcar (lambda (n) (/ n 2)) '(2 4 6 8 10))

16章でマクロは詳しく書いてあるらしい。楽しみ。

8章のコードをちゃんと読む。

と言いつつ、あんまりちゃんと読んでない。ソースコード全体はリンク先。
http://landoflisp.com/wumpus.lisp

  • make-city-edges関数
(defun make-city-edges ()
  (let* ((nodes (loop for i from 1 to *node-num*
                      collect i));ここまでnodesの変数宣言 nodes (loop ~)が初期値
         (edge-list (connect-all-islands nodes (make-edge-list)));edge-listの変数宣言
         (cops (remove-if-not (lambda (x);copsの変数宣言と(remove-if-not)以下が初期値
                                (zerop (random *cop-odds*)))
                              edge-list)));ここまででnodes,edge-list,copsの変数宣言部が終わる
    (add-cops (edges-to-alist edge-list) cops)));ここがletの本体 nodesはedge-list内で使っている
  • edges-to-alist関数
(defun edges-to-alist (edge-list)
  (mapcar (lambda (node1);lambdaの引数
            (cons node1;ここからlambdaの本体部
                  (mapcar (lambda (edge);入れ子のmapcarとlambda開始
                            (list (cdr edge)));lambdaの本体部おわり
                          (remove-duplicates (direct-edges node1 edge-list);このリストに入れ子lambdaをmapcar
                                             :test #'equal))));lambda閉じ
          (remove-duplicates (mapcar #'car edge-list))));ここのリストにlambdaをmapcarする
  • add-cops関数
(defun add-cops (edge-alist edges-with-cops)
  (mapcar (lambda (x) 
            (let ((node1 (car x));letの変数宣言
                  (node1-edges (cdr x)));letの変数宣言その2
              (cons node1;letの本体部
                    (mapcar (lambda (edge)
                              (let ((node2 (car edge)));let宣言
                                (if (intersection (edge-pair node1 node2);ifの条件式
                                                  edges-with-cops
                                                  :test #'equal);ifの条件式閉じ
                                    (list node2 'cops);ifがtの時実行
                                  edge)));ifがfのとき実行 入れ子lambda閉じ
                            node1-edges))));このリストを入れ子lambdaがmapcar 大きいletとlambdaも閉じる
          edge-alist));このリストを大きいlambdaが大きいmapcarする

Lispは括弧ばっかりで見た瞬間げんなりするようなことが多かったけど、インデントに意味があるようなので、慣れたらスラスラ読めるようになるのかも?ただletとかlambdaが入り組むと、括弧がどこで終わっているのかを追うのが難しい。構造を見てから読むようにしたいと思います。

まとめ

9章までで基本的なことが出尽くしたみたいなので、一通り復習してみました。ざっと読みなおしただけでも、いかに自分が理解しないまま進んでいたかがわかりますね(ものすごく基本的な概念ですら)。ただ、説明を優しくするためにletとlet*が離れた位置にあったり、あとの章に説明をまわしていたものもあったので、読み返す意味はあると思います。この本は読者に飽きさせないために概念をずらずら並べていないので、僕みたいに記憶力のない人間だと概念がごちゃごちゃになっちゃうんですよね。曖昧だったところを解消したので、10章からさくさく読んでいきたいと思います。