Tracks
/
Factor
Factor
/
Exercises
/
Garden Gathering
Garden Gathering

Garden Gathering

Learning Exercise

Introduction

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 ;

Setting and getting

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.

Scoped bindings with 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.

Mutable vectors

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 match

find (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 matches

reject (also in sequences) is the opposite of filter: it returns the elements that fail the predicate.

Instructions

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

1. Open the garden and list its registrations

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{ }

2. Register a plot

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.

3. Release a plot

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{ }

4. Get a registered plot

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

5. Find plots by name

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" } } }
Edit via GitHub The link opens in a new window or tab
Factor Exercism

Ready to start Garden Gathering?

Sign up to Exercism to learn and master Factor with 47 concepts163 exercises, and real human mentoring, all for free.