[R7RS+ base][SRFI-17] まずexpressionが評価されます。最初の形式では、symbolへの束縛が expressionの結果を指すように変更されます。 もしsymbolがローカルに束縛されていない場合は、グローバルな変数symbolが 存在していなければなりません。そうでなければエラーが報告されます。
2番目の形式はSRFI-17に定義されている「一般化されたset!」です。 これは構文的な装飾であり、実際は以下のように解釈されます。
((setter proc) arg ... expression)
CommonLispのsetf
とは、setter手続きに渡される引数の順序が異なることに注意して下さい。
例:
(define x 3) (set! x (list 1 2)) x ⇒ (1 2) (set! (car x) 5) x ⇒ (5 2)
[SRFI-210] 複数の変数を同時に更新します。Exprは与えられた変数と同じ 数だけの値を生成しなけばなりません。各値が対応するvarに セットされます。
(define a 0) (define b 1) (define c 2) (set!-values (a b) (values 3 4)) a ⇒ 3 b ⇒ 4 (set!-values (a b) (values b a)) a ⇒ 4 b ⇒ 3 (set!-values (a b . c) (values 1 2 3 4)) a ⇒ 1 b ⇒ 2 c ⇒ (3 4)
define-values
も参照 (定義)。
[SRFI-17] 手続きprocのsetter手続きを返します。 procがsetter手続きを持たない場合の動作は未定義です。
ある関数fのsetter手続きgとは、もし(g a b … v)
のように
呼ばれた場合、次の(f a b …)
がvを返すようになる手続きのことです。
ある手続きにsetter手続きを関連付けるには、setter
自身のsetter手続きを使うことが
できます。
(set! (setter f) g)
特定の手続きにsetter手続きを「ロック」することができます。システム既定のsetter手続き、
例えばcar
に対するset-car!
等はロックされていて、上記のような方法で
変更することは出来ません。ユーザ定義手続きにsetter手続きをロックするには下記の
getter-with-setter
を使います。
procが手続きでない場合は、object-apply
ジェネリックファンクションの
setterが返されます。これにより、適用可能オブジェクトが一般化されたset!
でもうまく動作します。詳しくは適用可能なオブジェクトを参照して下さい。
procがsetter手続きを持っている場合は#t
を返します。
[SRFI-17] 2つの手続き、getとsetを取り、新しい手続きを返します。 新しい手続きはgetと同じ動作をし、そのsetter手続きはsetにロックされて います。
インライン化可能な手続きにsetterがロックされている場合、一般化されたset!
は
コンパイラによってインライン展開される場合があります。
インライン化可能な手続きの定義については定義を参照してください。
Gaucheでは、一般化されたset!
と同じセマンティクスを持ついくつかのマクロが定義
されています。これらはset!を使った形に展開されます。
itemとplaceの値をコンスし、その結果を再びplaceにセットします。
placeはset!
と同様に、
変数か(proc arg …)
という形式でなければなりません。
このマクロの戻り値は未定義です。
(define x (list 2)) (push! x 3) x ⇒ (3 2) (push! (cdr x) 4) x ⇒ (3 4 2)
placeがリストの場合、だいたいこんなふうに展開されます。
(push! (foo x y) item) ≡ (let ((tfoo foo) (tx x) (ty y)) ((setter tfoo) tx ty (cons item (tfoo tx ty))))
註:Common Lispのpush
マクロは引数を逆の順番で取ります。
push!
は他の副作用を持つ形式との互換性を考えてこの順番としました。
Perlのpush
関数はpush!
マクロと同じ引数順ですが、
itemはシーケンスの末尾に追加されます (Perlのunshift
の方が
push!
の動作に近いです)。
Perlのpushオペレータの動作が必要ならQueueが使えます (data.queue
- キュー参照)。
placeの値はリストでなければなりません。itemがそのリスト中に なければ、placeがitemを先頭にコンスした値に更新されます。 itemが既にplaceの値の中にあれば、何も起きません。
itemが既にある値と等しいかどうかはequal手続きで比較されます。
省略された場合はeqv?
が使われます。
Common Lispのpushnew
に似ていますが、引数の順序が異なります。
data.queue
のenqueue-unique!
とqueue-push-unique!
も
参照してください(data.queue
- キュー)。
(define v (vector '("a"))) (push-unique! (vector-ref v 0) "b") v ⇒ #(("b" "a")) (push-unique! (vector-ref v 0) "A" string-ci=?) v ⇒ #(("b" "a")) (push-unique! (vector-ref v 0) "A") v ⇒ #(("A" "b" "a"))
placeの値を取り出し、そのcdr
をplaceにセットします。
元の値のcar
を返します。
(define x (list 1 2 3)) (pop! x) ⇒ 1 x ⇒ (2 3) (define x (vector (list 1 2 3))) x ⇒ #((1 2 3)) (pop! (vector-ref x 0)) ⇒ 1 x ⇒ #((2 3))
註:この動作はCommon Lispのpop
と同じです。
Perlのpop
はシーケンスの末尾から値を取ります。
Perlならshift
がpop!
の動作に近いです。
placeの値を評価します。それは数値にならなければなりません。
その値にdeltaが加算(inc!
)もしくは減算(dec!
)され、
結果がplaceに格納されます。deltaの既定値は1です。
Common Lispのincf
とdecf
に似ていますが、
戻り値を使うことは出来ません。
push!
等のマクロの一般化された形式です。
procは一つの引数を取り、一つの値を返す手続きでなければなりません。
placeの値がprocに渡され、procの結果がplaceに格納されます。
(define a (cons 2 3)) (update! (car a) (lambda (v) (* v 3))) a ⇒ (6 . 3) (update! (cdr a) (cut - <> 3)) a ⇒ (6 . 0)
次のコードと概ね同じです:
(let ((tmp0 place0) (tmp1 place1) ... (tmpN placeN)) (set! place0 tmp1) (set! place1 tmp2) : (set! placeN tmp0))
但し、place
が(proc arg ...)
の形であった場合、
各argは1度しか評価されません。
二ヶ所の値を交換するのにも使えます:
(let ((x (vector 1 2))) (rotate! (~ x 0) (~ x 1)) x) ⇒ #(2 1)
これはCommon Lispのrotatef
に似ています。