Іноді частину стану потрібно спільно використовувати кільком словам, не протягуючи його щоразу через їхні стекові ефекти. Динамічні змінні у Factor, оголошені за допомогою SYMBOL:, дають кожному слову доступ до значення, збереженого в просторі імен.
SYMBOL: і SYMBOLS:
SYMBOL: name оголошує одну змінну. 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
Динамічна змінна підтримує також другий варіант, з областю видимості. set / get / change (без суфікса -global) читають і записують поточну динамічну область видимості, а with-variable (у namespaces) створює нову область видимості на час виконання quotation:
with-variable ( value variable quot -- )
set ( value variable -- )
get ( variable -- value )
change ( variable quot: ( old -- new ) -- )
Змінна привʼязана до 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: він повертає елементи, які не задовольняють предикат.
Обʼєднання мешканців просить нас вести реєстрацію ділянок у саду. Стан зберігається у двох динамічних змінних:
registrations зберігає вектор кортежів plot, наразі зареєстрованих за певною особою.next-id зберігає ціле число, яке треба використати для наступної реєстрації.Кортеж plot має два поля:
| поле | тип |
|---|---|
id |
ціле число |
registered-to |
рядок тексту (англ. string) |
Визначмо open-garden, щоб ініціалізувати динамічні змінні: порожній вектор для registrations і 1 для next-id. Потім визначмо list-registrations, яка повертає поточний вектор ділянок.
open-garden
list-registrations .
! => V{ }
Визначмо register, щоб вона брала імʼя зі стека, створювала новий plot із наступним вільним id, додавала його до вектора registrations, збільшувала next-id на одиницю і повертала новий кортеж 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" } } }
Ідентифікатори ділянок мають бути унікальними й зростати навіть після звільнення: next-id ніколи не повинен використовувати те саме значення вдруге.
Визначмо release, щоб вона брала id і вилучала відповідний запис із registrations. Звільнення невідомого id не виконує жодних дій.
open-garden
"Emma" register drop
1 release
list-registrations .
! => V{ }
Визначмо get-registration, щоб вона брала id і повертала відповідний кортеж plot, або символ not-found, якщо жодна ділянка не має такого id.
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" } } }
Зареєструйтеся на Exercism, щоб вивчати й опановувати Factor, а також 47 концепцій163 вправи та справжнє наставництво від людей, і все це безкоштовно.