実践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