プログラミングを頑張る日記

プログラミングを勉強して、ハッカーになろう

Common Lispを頑張る(19)

土曜日です。皆さんいかがお過ごしでしょうか。
台風が来るらしく天気が悪いので、僕はひきこもりです。
今回は、「実践Common Lisp」の変数の章を終わらせます。

定数

定数変数というものがあるらしいです。凄い字面です。
定数変数は全てグローバルで、DEFCONSTANTによって定義されます。
いったん定数として定義すると、その名前は定数の参照にしか使えず、
関数のパラメータや他の束縛フォームで束縛し直せなくなります。

そんな感じになっちゃうので、定数を定義する時は名前の前後に
"+"をつける命名規約が(そんなに一般的ではないにしろ)あるそうです。
定数を使う機会がどれだけあるかまだわかりませんが、そうします。

代入

束縛を作成したら、現在の値を得ること、新しい値を設定することができます。
現在の値を得るには変数を参照すればいいだけです。
新しい値を代入する時はSETFマクロが使えます。
知ってることばかりですが、下記の様に
一度に複数の代入をできるのは知らなかったです(忘れてるだけかも)。

CL-USER>
(funcall #'(lambda ()
		      (let ((x)
			    (y))
			(setf x 1 y 2))))
2
CL-USER>

また上の例でもわかりますが、SETFは代入した値を返すので
入れ子にして使うこともできます。

一般化代入

値を保持できる場所は変数の束縛だけではないそうです。
配列やらリストやらの色々なデータ構造がサポートされているし、
ユーザ定義のデータ型もサポートされています。
それらのデータ構造は、それぞれに値が保持されている
複数の場所から構成されているとのことです。

ここでは代入に関する説明だけしてくれるそうです。
まず、どんなデータ構造への代入にもSETFが使えるそう。
SETFは代入という観点では、ほとんどの言語の"="と同じだそうです。

(setf (aref a 0) 10) ;arefは配列のアクセス関数。配列aの要素0に10を代入
(setf (gethash 'key hash) 10) ;gethashはハッシュテーブルを検索。hashのkeyに10を代入
(setf (field o) 10) ; ユーザ定義オブジェクトのfieldというスロットに10を代入

まあ、代入したいならSETF使えばいいってことですね。

SETF以外

まあ上ではSETFだけでOKと書きましたが、C言語でも

x = x + 1;
// ↑でもいいけど↓のがよく使うよね。
x++;

という書き方があるように、現在の値をもとに新しい値を代入する際は
それ専用のオペレータを使うのが一般的だそうです。

CL-USER> (defparameter *x* 0)
*X*
CL-USER> (incf *x*)
1
CL-USER> (decf *x*)
0
CL-USER> (incf *x* 10)
10
CL-USER> (decf *x* 5)
5
CL-USER> *x*
5
CL-USER>

特に説明の必要はなさそうな感じですね。
INCFやDECFはモディファイマクロという種類のマクロだそうです。
これらはSETFの上に作られています。

モディファイマクロの利点はSETFを使った場合より明快に書けることだそう。
参考書例を理解します。配列のある要素を適当に増加させる関数です。
まずは配列の作り方から勉強しないと…。
VECTORとMAKE-ARRAYがあるんですね。ふむふむ。

CL-USER> (defparameter *array* (make-array 5 :initial-element 0))
*ARRAY*
CL-USER> *array*
#(0 0 0 0 0)
CL-USER> (incf (aref *array* (random (length *array*))))
1
CL-USER> *array*
#(0 0 0 0 1)
CL-USER> (incf (aref *array* (random (length *array*))))
1
CL-USER> *array*
#(0 1 0 0 1)
CL-USER>

うん、できているようです。
lengthで*array*の要素数を数えて、それ以下の値をrandomで生成、
その要素をarefで引っこ抜いてincfで1を加算して配列を変更していると。
これを素直にSETFで書き直すと…

CL-USER> (setf (aref *array* (random (length *array*)))
	       (1+ (aref *array* (random (length *array*)))))
1
CL-USER> *array*
#(0 1 1 0 1)
CL-USER> (setf (aref *array* (random (length *array*)))
	       (1+ (aref *array* (random (length *array*)))))
2
CL-USER> *array*
#(0 1 1 2 1)
CL-USER>

しかしまともには動きません。2つのrandomの戻り値が一致しないからです。
最初に引っこ抜く時に生成したrandomの値を覚えておけばいいだけですが。
一般にモディファイマクロは引数及び場所を表すフォームの一部分が
正確に一度だけ左から右に評価されることが保証されているそうです。

最後に便利なモディファイマクロを紹介してくれます。
ROTATEFとSHIFTFです。

CL-USER> (defparameter *x* 111)
*X*
CL-USER> (defparameter *y* 999)
*Y*
CL-USER> (defun disp-x-y ()
	   (princ `(*X*の値は ,*x* *Y*の値は ,*y* です)))
DISP-X-Y
CL-USER> (disp-x-y)
(*X*の値は 111 *Y*の値は 999 です)
(*X*の値は 111 *Y*の値は 999 です)
CL-USER> (rotatef *x* *y*)
NIL
CL-USER> (disp-x-y)
(*X*の値は 999 *Y*の値は 111 です)
(*X*の値は 999 *Y*の値は 111 です)
CL-USER>

ROTATEFは引数の値を右から左に(一番左のものは一番右へ)入れ替えるようですね。
disp-x-yではFORMAT関数を使うのが面倒だったのでこうなりました。

SHIFTFはその名の通り変数の値を左から右にずらすとのこと。

CL-USER> (defparameter *x* 0)
*X*
CL-USER> (defparameter *y* 10)
*Y*
CL-USER> (defparameter *z* 100)
*Z*
CL-USER> (list *x* *y* *z*)
(0 10 100)
CL-USER> (shiftf *x* *y* *z* 1000)
0
CL-USER> (list *x* *y* *z*)
(10 100 1000)
CL-USER>

結果表示用の関数なんてlistがあれば作る必要なかったんや!
というのはともかく、変数をずらしたい順に並べて、
最後に一番右の変数に代入したい定数を置いてやればいい感じですかね。

とりあえずこれで変数の章は終わりです。
次の章はマクロですが…いったん「Land of Lisp」に帰ろうかな。

まあ、明日考えます。台風に気をつけましょう。