初めての人のためのLISP - 第12講 ガールフレンドも買い物も関数引数でOK

初めての人のためのLISP[増補改訂版]のメモです。
最初:初めての人のためのLISP (第1講-第5講) - もうカツ丼でいいよな

関数引数の基本はapply

applyの第1引数は関数で、第2引数がその関数の引数のリスト(第11講)。
数値だけが入ったリストの総和を計算する。

(setq x '(1 2 3 4 5 6))
(apply '+ x) ; => 21

funcall

funcallを使うと引数をリストにしなくてもよい。

(funcall '+ 1 2 3 4 5 6) ; => 21

mapcar

mapcar関数はリストの各々の要素に関数をapplyした結果をリストとして返す。

(setq x '(1 2 3 4 5))
(mapcar (lambda (x) (* x x)) x) ; => (1 4 9 16 25)

定義

(defun mapcar (fn mlist)
       (cond ((null mlist) nil)
             (t (cons (funcall fn (car mlist))
                      (mapcar fn (cdr mlist)) ))))

carに関数を適用して結果をconsしていく。

maplist

mapcar関数がcarに関数を適用した結果をconsするのに対し、maplist関数はlistに関数を適用していく。listは順次cdrをとっていく。定義はmapcarとほぼ同じ。

(defun maplist (fn mlist)
       (cond ((null mlist) nil)
             (t (cons (funcall fn mlist)) ; <= ここが違う
                      (maplist fn (cdr mlist)) )))

使用場面はあまりないが、例えばリストの累積和を求める場合に使える。

(setq x '(1 3 5 7 9))
(reverse
        (maplist (lambda (x) (apply '+ x))
                 (reverse x)) ) ; => (1 4 9 16 25)

mapc

mapcはmapcarとよく似ているが、返り値は引数として渡したもとのリストなので、関数に何らかの副作用(破壊的なリストの変更やその他出力)がなければ引数として渡したリストがそのまま返る。

(defun mapc (fm mlist)
       (do ((x mlist (cdr x))) ; リストはコピーしてから使う
           ((null x) mlist)    ; コピーが空になったら元のリストを返す
           (funcall fn (car x)) ))

mapl

maplはmapcarに対するmaplistのような関数で、リストに対し関数を適用する。定義はmapcとほぼ同じ。

(defun mapl (fm mlist)
       (do ((x mlist (cdr x)))
           ((null x) mlist)
           (funcall fn x)) ) ; <= ここが違う

mapcan

mapcanはmapcarと同様リストの要素それぞれに関数を適用するが、結果はnconcで結合されて返される。そのため、適用する関数の返り値がnilになる場合その部分は飛ばされる。また、nconcはリストをつなげる関数なので、適用する関数の返り値をリストにするという点に注意する。

(setq x '(1 5 3 4 2 5))
(mapcar (lambda (x) (cond ((< x 3) (list x)))) x)
; => ((1) NIL NIL NIL (2) NIL)
(mapcan (lambda (x) (cond ((< x 3) (list x)))) x)
; => (1 2)

mapcon

mapconはmaplistやmaplと同様にリストを対象としたバージョンのmapcan。

#'(シャープクォート)

#'をシンボル名の頭に付けると、そのシンボルがもつ関数実体を返す。同じシンボル名は変数と関数を同時に持つことが出来、#'の有無でどちらにアクセスするかを選べる。

(defun square (x) (* x x))
(setq square 5)
(square square) ; => 25
square ; => 5
#'square ; => #<FUNCTION SQUARE>

関数実体を参照して得られた関数はapplyやfuncallで実行できる。

(apply #'square '(5)) ; => 25
(funcall #'square 10) ; => 100

静的スコープとクロージャ

通常、ある関数から別の関数の変数を見ることはできない。

;; 2つの関数をそれぞれ独立に定義
(defun foo (x y) (bar x))  ; yは関数barに渡したい
(defun bar (z) (cons y z)) ; 変数yは関数foo中のyのつもりだが…
(foo 1 2) ; => エラー!

この仕組は静的スコープと呼ばれる。
しかし、関数の中でラムダ式が作られると、その時点でアクセス可能な変数はそのラムダ式からもアクセス可能となる。

;; 上記の関数barをラムダ式として定義
(defun foobar (x y)
       (funcall (lambda (z) (cons y z)) x))
(foobar 1 2) ; => (2 . 1)
;; ラムダ式からは親の関数foobarの変数yが見える

これを内部のラムダ式が外側で宣言された変数を「閉じ込めた」と見ることができる。このことからラムダ式によって作られた無名の関数実体を関数閉包(function closure)またはクロージャと呼ぶ。

sort

関数sortは指定された関数に従ってリストの要素を破壊的に並び替える。

(setq x '(1 5 4 3 5 3))
(sort x #'<) ; => (1 3 3 4 5 5)
;; (< left right)の関係が成り立つようソート