状態をいくつかのワードの間で共有したいけれど、そのたびにスタック効果を通して受け渡すのは避けたい、ということがあります。Factorの動的変数は、SYMBOL:で宣言し、名前空間に保存された値に各ワードからアクセスできるようにします。
SYMBOL:とSYMBOLS:
SYMBOL: nameは変数を1つ宣言します。SYMBOLS: a b c ;を使うと、複数をまとめて宣言できます。
USING: namespaces ;
SYMBOLS: counter total ;
set-globalとget-global(namespacesにあります)は、グローバル名前空間を操作します。
set-global ( value variable -- )
get-global ( variable -- value )
change-global ( variable quot: ( old -- new ) -- )
0 counter set-global
counter get-global . ! => 0
counter [ 10 + ] change-global
counter get-global . ! => 10
モジュールレベルの状態を扱うなら、-globalが付いたほうを使います。
with-variableによるスコープ付きの束縛動的変数には、もう1つスコープ付きの使い方があります。set / get / change(-globalが付かないほう)は現在の動的スコープを読み書きし、with-variable(namespacesにあります)はクオーテーションが実行されている間だけ有効な新しいスコープを作ります。
with-variable ( value variable quot -- )
set ( value variable -- )
get ( variable -- value )
change ( variable quot: ( old -- new ) -- )
quotが実行されている間、変数にはvalueが束縛されます。quotが戻ると、その束縛は消えます。これにより、外側の呼び出し元が値を「注入」して、内側のコードがスタックシグネチャをいちいち経由せずにその値を読めるようになります。
SYMBOL: greeting
"Hello" greeting [
greeting get . ! => "Hello"
"Howdy" greeting set
greeting get . ! => "Howdy"
greeting [ "!" append ] change
greeting get . ! => "Howdy!"
] with-variable
greeting get-global . ! => f (the scoped binding is gone)
この演習では全体を通してグローバルなほうを使いますが、set/get/changeという対になるファミリーを知っておくと、他の人のコードを読むときに役立ちます。
動的変数は参照を保持するので、その中にミュータブルなコレクションを入れておき、その場で更新できます。
SYMBOL: history
V{ } clone history set-global
"first event" history get-global push
"second event" history get-global push
history get-global .
! => V{ "first event" "second event" }
V{ … }はベクターのリテラルです。V{ }のリテラルを保存する前には、必ずcloneしてください。リテラルは呼び出しの間で共有されるからです。
find:最初に一致する要素find(sequencesにあります)は、述語が真になる最初の要素と、そのインデックスを返します。
find ( seq quot -- index elt/f )
一致するものがなければ、どちらの値もfです。nipでインデックスを落とし、unless*を使うのが、「見つからなかったときの既定値」の定番の組み合わせです。
reject:一致する要素を落とすreject(こちらもsequencesにあります)はfilterの反対で、述語を満たさない要素を返します。
地域の組合から、庭の区画の登録を管理するよう頼まれています。状態は2つの動的変数に保持されます。
registrations — 現在ある人に割り当てられているplotタプルのベクターです。next-id — 次の登録に使う整数です。plotタプルには2つのスロットがあります。
| スロット | 型 |
|---|---|
id |
整数 |
registered-to |
文字列 |
open-gardenを定義して、動的変数を初期化します。registrationsには空のベクター、next-idには1を設定します。次にlist-registrationsを定義して、現在の区画のベクターを返します。
open-garden
list-registrations .
! => V{ }
registerを定義します。スタックから名前を取り出し、次に使えるidで新しいplotを作り、registrationsベクターに追加し、next-idを1つ増やして、新しいplotを返します。
open-garden
"Emma Balan" register .
! => T{ plot { id 1 } { registered-to "Emma Balan" } }
list-registrations .
! => V{ T{ plot { id 1 } { registered-to "Emma Balan" } } }
区画のidは一意でなければならず、解放後も増え続けます。next-idが同じ値を使い回すことはありません。
releaseを定義します。idを受け取り、一致する項目をregistrationsから削除します。存在しないidを解放しても何も起こりません。
open-garden
"Emma" register drop
1 release
list-registrations .
! => V{ }
get-registrationを定義します。idを受け取り、一致するplotを返します。そのidの区画がなければ、シンボルnot-foundを返します。
open-garden
"Emma" register drop
1 get-registration .
! => T{ plot { id 1 } { registered-to "Emma" } }
7 get-registration .
! => not-found
find-by-nameを定義します。名前を受け取り、その人に現在登録されているすべての区画のベクターを返します。
open-garden
"Emma" register drop
"Bob" register drop
"Emma" register drop
"Emma" find-by-name .
! => V{ T{ plot { id 1 } { registered-to "Emma" } }
T{ plot { id 3 } { registered-to "Emma" } } }