Sometimes a piece of state needs to be shared between several words
without threading it through their stack effects every time. Factor's
dynamic variables — declared with SYMBOL: — give each word
access to a value stored in a namespace.
SYMBOL: and SYMBOLS:
SYMBOL: name declares one variable. SYMBOLS: a b c ; declares
several at once:
USING: namespaces ;
SYMBOLS: counter total ;
set-global and get-global (in namespaces) operate
on the global namespace:
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
For module-level state, the -global flavours are what you want.
with-variable
A dynamic variable also supports a second, scoped flavour. set /
get / change (no -global suffix) read and write the current
dynamic scope, and with-variable (in namespaces)
creates a fresh scope for the duration of a quotation:
with-variable ( value variable quot -- )
set ( value variable -- )
get ( variable -- value )
change ( variable quot: ( old -- new ) -- )
The variable is bound to value while quot runs; once it returns,
the binding disappears. This lets an outer caller "inject" a value
that inner code can read without threading it through every stack
signature.
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)
This exercise uses the global flavour throughout — but recognising
the parallel set/get/change family is useful when you read
other people's code.
Because dynamic variables hold a reference, you can keep a mutable collection inside one and update it in place:
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{ … } is a literal vector. Always clone a V{ } literal
before storing it: literals are shared between calls.
find — first matchfind (in sequences) returns the first element for
which a predicate is truthy, plus its index:
find ( seq quot -- index elt/f )
If nothing matches, both values are f. Dropping the index with
nip and using unless* is the standard "default if missing"
combination.
reject — drop the matchesreject (also in sequences) is the opposite of
filter: it returns the elements that fail the predicate.
Your community association is asking you to manage the garden's plot registrations. The state lives in two dynamic variables:
registrations — a vector of plot tuples currently assigned
to a person.next-id — the integer to use for the next registration.The plot tuple has two slots:
| slot | type |
|---|---|
id |
integer |
registered-to |
string |
Define open-garden to initialise the dynamic variables: an empty
vector for registrations, and 1 for next-id. Then define
list-registrations to return the current vector of plots.
open-garden
list-registrations .
! => V{ }
Define register to take a name off the stack, build a fresh plot
with the next available id, append it to the registrations vector,
bump next-id by one, and return the new 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" } } }
Plot ids must be unique and increase even after a release — next-id
should never reuse a value.
Define release to take an id and remove the matching entry from
registrations. Releasing an unknown id is a no-op.
open-garden
"Emma" register drop
1 release
list-registrations .
! => V{ }
Define get-registration to take an id and return the matching
plot, or the symbol not-found if no plot has that id.
open-garden
"Emma" register drop
1 get-registration .
! => T{ plot { id 1 } { registered-to "Emma" } }
7 get-registration .
! => not-found
Define find-by-name to take a name and return a vector of all plots
currently registered to that person.
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" } } }
Sign up to Exercism to learn and master Factor with 47 concepts163 exercises, and real human mentoring, all for free.