実践CommonLisp:関数、変数、マクロ
関数
(defun name (parameter*) "ドキュメンテーション文字列:省略可能" body-form*)
パラメータ
- オプショナルパラメータ(&optional)
- 引数の値にデフォルトを指定できる。指定なしの場合、NILになる。
- デフォルト値かどうかは、デフォルト値のあとにもう一つ値を指定する。この変数の真偽で判断が可能
CL-USER> (defun foo(a &optional b) (list a b)) FOO CL-USER> (foo 1) (1 NIL) CL-USER> (defun foo(a &optional (b 0 default-p)) (list a b default-p)) FOO CL-USER> (foo 1) (1 0 NIL) CL-USER> (foo 1 2) (1 2 T)
- レストパラメータ(&rest)
- 引数ののこりをリストにくくってくれる。
CL-USER> (defun foo (a &rest values) (* a (reduce #'+ values))) FOO CL-USER> (foo 2 2 3) 10
- キーワードパラメータ
- キーワードを指定することで、同じ名前のシンボルに束縛される
CL-USER> (defun foo (a &key b c) (* a (- c b))) FOO CL-USER> (foo 3 :b 4 :c 5) 3 CL-USER> (foo 3 :c 4 :b 5) -3
高階関数
関数を引数にとる関数。関数オブジェクトを起動するのにはfuncallまたはapplyをつかう。
funcallは引数の数がはっきりしているときに使う。applyは引数としてリストを指定する。
関数オブジェクトは特殊オペレータFUNCTIONでえられる。これの構文糖は#'。
CL-USER> (funcall #'+ 1 2 3) 6 CL-USER> (apply #'+ (list 1 2 3)) 6
無名関数
LAMBDA式を使って無名関数が作れる。
CL-USER> (funcall #'(lambda (a b c) (+ a b c)) 1 2 3) 6
変数
値を保持できる名前付きの場所。
lispは関数呼び出しごとに引数保持のために新たな束縛(binding)をつくり、スコープは導入したフォームに制限される。
関数呼び出しのフォームやLETは束縛フォーム(binding form)tお呼ばれ、スコープはそれぞれで区切られる。
CL-USER> (defun foo (a) (let ((a 10)) (format t "~a~%" a))) FOO CL-USER> (foo 1) 10 NIL
束縛フォームが導入する変数はレキシカルスコープで、束縛は必要とされている間、ずっとくっついてまわる。無名関数はこのような形で束縛を保持できる。これをクロージャ(closure:閉包)という。
代入
setfマクロを使う。
配列要素へはaref、ハッシュテーブルにはgethash、ユーザ定義オブジェクトにはフィールド名を、それぞれ組み合わせて使う。
CL-USER> (setf x (make-array 2)) #(NIL NIL) CL-USER> (setf (aref x 0) 10) 10 CL-USER> x #(10 NIL) CL-USER> (setf x (make-hash-table)) #S(HASH-TABLE :TEST FASTHASH-EQL) CL-USER> (setf (gethash 'a x) 10) 10 CL-USER> x #S(HASH-TABLE :TEST FASTHASH-EQL (A . 10)) CL-USER> (defclass fooclass () ((x :initarg :x :initform 10 :accessor x))) #<STANDARD-CLASS FOOCLASS> CL-USER> (setf x (make-instance 'fooclass)) #<FOOCLASS #x19F78BB9> CL-USER> (x x) 10 CL-USER> (setf (x x) 20) 20 CL-USER> (x x) 20
モディファイマクロ
現在の値に基づいて新しい値を代入する。
incf、decf、push、popなど。
CL-USER> (setf x 1) 1 CL-USER> x 1 CL-USER> (incf x) 2 CL-USER> x 2 CL-USER> (decf x) 1 CL-USER> (setf x '(1)) (1) CL-USER> (push 2 x) (2 1) CL-USER> (pop x) 2 CL-USER> x (1)
マクロ
コードジェネレータ。
マクロ展開時(macro expansion time)と実行時(runtime)は明確に区別される。
マクロをつくる場合は、その展開形を書き、サンプルを書き、それを生成できるようなコードを書いていく。
そして抽象化に漏れがないか確認して、完成、とのこと。
CL-USER> (defmacro do-primes ((var start end) &body body) (with-gensyms (ending-value-name) `(do ((,var (next-prime ,start) (next-prime (1+ ,var))) (ending-value-name ,end)) ((> ,var ending-value-name)) ,@body))) DO-PRIME CL-USER> (defun primep (number) (when (> number 1) (loop for fac from 2 to (isqrt number) never (zerop (mod number fac))))) PRIMEP CL-USER> (defun next-prime (number) (loop for n from number when (primep n) return n)) NEXT-PRIME CL-USER> (do-primes (p 2 3) (print p)) 2 3 NIL CL-USER> (do-primes (p 2 19) (print p)) 2 3 5 7 11 13 17 19 NIL