Жонглювання стеком цілком годиться для коротких слів. Коли слово має кілька вхідних даних і до них потрібно звертатися за назвою, Factor пропонує локальні змінні: іменовані привʼязки, які вводять за допомогою :: (для визначень слів) або [| | ] (для інлайн-лямбд).
:: ( inputs -- outputs )Якщо замінити : на ::, назви вхідних даних стануть локальними змінними в тілі:
USING: locals ;
:: hypotenuse ( a b -- c )
a a *
b b *
+ sqrt ;
Порівняймо з версією, де значення переставляють на стеку:
! DOCTEST: SKIP (shown for comparison — would re-define hypotenuse)
: hypotenuse ( a b -- c )
[ sq ] bi@ + sqrt ;
Обидва варіанти правильні: локальні змінні особливо доречні, коли до одного вхідного значення звертаються більше ніж раз або коли природний порядок дій не збігається з порядком на стеку.
:>
Усередині тіла :: (або інлайн-лямбди [| ... |]) :> знімає значення зі стеку даних і привʼязує його до нової локальної змінної:
value :> name ! immutable binding
value :> name! ! mutable binding (name! is its setter)
Зазвичай використовують незмінну форму. Нею дають назву проміжному значенню, і тоді тіло читається згори вниз:
USING: locals math ;
:: average-3 ( x y z -- avg )
x y + z + :> sum
sum 3 / ;
Варіант із ! позначає локальну змінну як змінювану; далі в тілі value name! (без двокрапки й без :>) присвоює їй нове значення:
USING: kernel locals math math.order ;
:: clamped ( x lo hi -- y )
x :> v!
v lo < [ lo v! ] when
v hi > [ hi v! ] when
v ;
Змінювані локальні змінні особливо доречні в словах для ітерації, з якими ми познайомимося в наступних вправах (наприклад, while).
[let - область видимості звідусіль:> працює лише всередині лексичної області видимості, яку :: і [| ... |] створюють автоматично. Щоб увести локальні змінні зі звичайного слова : або в лістенері, обгорнімо код у [let ... ]:
[let code :> name code :> name ... body ]
Кожне :> name привʼязує значення на вершині стеку з попереднього виразу; ці привʼязки видно в решті форми.
USING: locals math ;
[let 3 :> x 4 :> y x x * y y * + ] . ! => 25
[let - це місток між визначеннями : у стековому стилі та іменованими привʼязками; він стає в пригоді, коли від локальних змінних виграє лише частина слова.
[| inputs | body ]
Усередині квотейшна [| inputs | body ] уводить локальні змінні, до яких тіло може звертатися за назвою. Це особливо зручно зі словами вищого порядку, як-от assoc-map, чий квотейшн отримує key і value:
USING: assocs locals ;
H{ { "a" 1 } { "b" 2 } }
[| key value | key value neg ] assoc-map .
! => H{ { "a" -1 } { "b" -2 } }
Та сама форма працює і для map-index (у [sequences][sequences]), чий квотейшн отримує елемент і його індекс. Назви ясно показують, який операнд яку роль відіграє:
USING: locals sequences ;
! Divide each element by its 1-based position:
{ 10 80 270 } [| elt i | elt i 1 + / ] map-index .
! => { 10 40 90 }
Стековий ефект лямбди визначають список вхідних даних і те, що породжує її тіло.
До локальних змінних варто вдаватися, коли:
dup/swap/rot для виправлення важко читати.assoc-map).Коли тіло є чистим лінійним потоком, звичайні визначення : залишаються коротшими й не менш читабельними.
assoc-map - по одному запису за разassoc-map (у assocs) застосовує квотейшн до кожного запису асоціативної колекції:
assoc-map ( assoc quot: ( key value -- newkey newvalue ) -- newassoc )
Лямбда з локальними змінними - найчистіший спосіб написати квотейшн.
cond зновуКоли потрібно розгалужуватися за f або за числовим значенням, cond - саме та форма, яка потрібна; case не потрібен, бо немає єдиного значення для порівняння.
Ми знову на кухні й удосконалюємо рецепт лазаньї з Смачнюща лазанья Лії. Завдання тут вимагають тримати в голові кілька вхідних даних одночасно, а саме тоді locals, іменовані привʼязки Factor, стають у пригоді.
Визначте cooking-status, щоб вона брала зі стека показник таймера та повертала рядок тексту (англ. string) зі станом.
0 → "Lasagna is done."
f (таймер не встановлювали) → "You forgot to set the timer."
"Not done, please wait."
12 cooking-status . ! => "Not done, please wait."
0 cooking-status . ! => "Lasagna is done."
f cooking-status . ! => "You forgot to set the timer."
Визначте preparation-time, щоб вона приймала масив назв шарів і середню кількість хвилин на шар та повертала загальний час підготовки.
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } 3 preparation-time .
! => 18
Визначте quantities, щоб вона приймала масив назв шарів і повертала два значення: грами потрібної локшини (50 г на шар локшини) та літри потрібного соусу (1/5 літра на шар соусу).
{ "sauce" "noodles" "sauce" "meat" "mozzarella" "noodles" } quantities .s
! => 100
! => 2/5
Друг надсилає список інгредієнтів; останній елемент - це його секрет. Визначте add-secret-ingredient, щоб вона приймала його список і наш список та повертала наш список із доданим секретом.
{ "noodles" "sauce" "mozzarella" "kampot pepper" }
{ "noodles" "meat" "sauce" "mozzarella" }
add-secret-ingredient .
! => { "noodles" "meat" "sauce" "mozzarella" "kampot pepper" }
Рецепт із книги розрахований на дві порції. Визначте scale-recipe, щоб вона приймала рецепт (хеш-таблицю від назви інгредієнта до кількості) і цільову кількість порцій та повертала новий рецепт, відповідно масштабований.
H{ { "noodles" 200 } { "sauce" 1/2 } { "mozzarella" 1 } { "meat" 100 } }
4 scale-recipe .
! => H{ { "noodles" 400 } { "sauce" 1 } { "mozzarella" 2 } { "meat" 200 } }
Зареєструйтеся на Exercism, щоб вивчати й опановувати Factor, а також 47 концепцій163 вправи та справжнє наставництво від людей, і все це безкоштовно.