دستکاری پشته برای توابع کوتاه خوب است. وقتی یک تابع چند ورودی دارد و لازم است با اسم به آنها ارجاع بدهید، 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 نیازی نیست، چون مقدار واحدی برای مقایسه وجود ندارد.
شما به آشپزخانه برگشتهاید و دارید دستور لازانیا را از لازانیای خوشمزهی لی بهتر میکنید. کارهای اینجا از شما میخواهند چند ورودی را همزمان مدیریت کنید و این دقیقاً همان جایی است که «متغیرهای محلی»، یعنی اتصالهای بانامِ Factor، به کار میآیند.
تابع cooking-status را تعریف کنید تا یک مقدار تایمر را از پشته بردارد و یک رشتهی وضعیت برگرداند.
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 را تعریف کنید تا آرایهای از اسم لایهها بگیرد و دو مقدار برگرداند: گرم رشتهفرنگی لازم (۵۰ گرم برای هر لایهی رشتهفرنگی) و لیتر سس لازم (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 } }