مسیرها
/
Factor
Factor
/
تمرین‌ها
/
شابلونهای علامت
شابلونهای علامت

شابلونهای علامت

تمرین یادگیری

مقدمه

شما پیشاپیش می‌دانید چگونه یک quotation را از تکه‌ها بسازید و آن را اجرا کنید. quotation چیزی جز دنباله‌ای از کد نیست، پس همان واژه‌های دنباله‌ای که می‌شناسید برای آن هم به کار می‌آیند:

USING: kernel math prettyprint quotations sequences ;

{ [ 1 ] [ 2 + ] [ 3 * ] } concat >quotation call .   ! => 9
[ 1 + ] dup append .                                 ! => [ 1 + 1 + ]

append دو quotation را به هم می‌چسباند، concat یک آرایه از آن‌ها را مسطح می‌کند، map داده را به quotation تبدیل می‌کند و >quotation نتیجه را قابل فراخوانی می‌سازد. MACRO: دقیقاً همین مهارت را برمی‌دارد و آن را به زمان کامپایل منتقل می‌کند.

ماکرو چیست

یک تعریف MACRO: ظاهری مثل یک واژه‌ی معمولی دارد، اما بدنه‌ی آن در حالی که برنامه‌تان در حال کامپایل است اجرا می‌شود، نه در حالی که اجرا می‌شود. بدنه باید یک quotation روی پشته باقی بگذارد. سپس کامپایلر آن quotation را در محل فراخوانی جاسازی می‌کند؛ یعنی فراخوانی ماکرو را با کدی که بدنه تولید کرده است جایگزین می‌کند. در زمان اجرا هیچ ماکرویی باقی نمی‌ماند، فقط کد تولیدشده می‌ماند.

USING: kernel macros sequences ;

MACRO: twice ( quot -- spliced )
    dup append ;

هر جا [ 1 + ] twice را بنویسید، کامپایلر:

  1. بدنه را با [ 1 + ] روی پشته اجرا می‌کند،
  2. dup append quotation [ 1 + 1 + ] را می‌سازد،
  3. کد آن، یعنی 1 + 1 +، را در همان جا جاسازی می‌کند.

پس واژه‌ای که twice را فراخوانی می‌کند، طوری کامپایل می‌شود که گویی 1 + 1 + را با دست نوشته‌اید:

: inc-twice ( x -- y ) [ 1 + ] twice ;

5 inc-twice .   ! => 7

تفاوت کلیدی این است: یک واژه‌ی معمولی که quotation می‌گیرد، آن را در زمان اجرا call می‌کند. ماکرو هرگز چیزی را فراخوانی نمی‌کند؛ بلکه کد تازه را به کامپایلر می‌دهد تا در جای آن کامپایل کند.

ورودی‌ها باید مقدار ثابت باشند

چون بدنه در زمان کامپایل اجرا می‌شود، ورودی‌های یک ماکرو باید در همان زمان معلوم باشند. آن‌ها باید در محل فراخوانی مقدار ثابت باشند: یک quotation از نوع [ … ]، یک عدد، یک آرایه، یک رشته. مقداری که فقط در زمان اجرا وجود دارد را نمی‌توان به یک ماکرو سپرد.

! DOCTEST: SKIP — illustrative call sites, not standalone code
[ 1 + ] twice          ! ok: the quotation is a literal
[ 1 + ] 3 repeat-quot  ! ok: the quotation and the number are literals

جعبه‌ابزار

بدنه‌ی یک ماکرو کد معمولی‌ای است که یک quotation برمی‌گرداند. بیشتر کار را همان واژه‌هایی انجام می‌دهند که پیشاپیش دیده‌اید:

برای ساختن… از این استفاده کنید
[ A B ] از [ A ] و [ B ] append
یک quotation از یک آرایه از آن‌ها concat >quotation
یک quotation برای هر عنصر یک دنباله map، سپس concat >quotation

دو واژه‌ی دیگر از sequences و kernel این جعبه‌ابزار را کامل می‌کنند.

<repetition> (در sequences) دنباله‌ای از n نسخه از یک عنصر می‌سازد. اثر پشته‌ای آن ( length elt -- repetition ) است و تعداد اول می‌آید. نتیجه یک دنباله‌ی مجازی است، پس آن را به روش معمول مسطح کنید:

3 [ 1 + ] <repetition> concat >quotation .   ! => [ 1 + 1 + 1 + ]

curry (در kernel) یک مقدار را به ابتدای یک quotation اضافه می‌کند و quotation تازه‌ای تولید می‌کند که اول آن مقدار را روی پشته می‌گذارد:

3 [ + ] curry .          ! => [ 3 + ]
{ 1 2 3 } [ . ] curry .  ! => [ { 1 2 3 } . ]

curry همان راهی است که با آن یک عنصر مشخص را در یک quotation جای می‌دهید، دقیقاً همان چیزی که { 10 20 30 } و [ + ] را به [ 10 + ] [ 20 + ] [ 30 + ] تبدیل می‌کند.

چرا این کار ساده‌تر از آن است که به نظر می‌رسد

در بعضی زبان‌ها، ماکروها به نگارش ویژه‌ی quote/unquote و سازوکار اضافی نیاز دارند تا از تداخل نام‌ها جلوگیری کنند. Factor به هیچ‌کدام از این‌ها نیاز ندارد: یک quotation از پیش دنباله‌ای از واژه‌های حل‌شده است؛ تجزیه‌گر هنگام خواندن [ … ] هر dup و + و append را جست‌وجو کرده است. پس بدنه‌ی یک ماکرو فقط همان دستکاری دنباله است که از قبل انجام می‌دهید و تصادفاً یک quotation برمی‌گرداند.

خویشاوند نزدیک: $[ … ]

یک ماکرو quotationای برمی‌گرداند تا در یک فراخوانی جاسازی شود. اما وقتی در عوض می‌خواهید یک مقدار ساده در زمان تجزیه محاسبه شود، $[ code ] هنگام خوانده شدن فایل، code را اجرا می‌کند و نتیجه را به صورت یک مقدار ثابت در متن منبع جای می‌دهد:

! DOCTEST: SKIP — compile-time value, shown for contrast
USING: calendar literals ;

CONSTANT: build-time $[ now ]

$[ … ] از واژگان literals می‌آید که واژه‌های درج نتایج زمان تجزیه در مقدارهای ثابت را گرد آورده است. این را فقط برای کامل بودن مطلب آورده‌ایم. چهار شابلونی که خواهید نوشت، یعنی twice و repeat-quot و compose-many و each-literal، همه تعریف‌های MACRO: هستند.

دستورالعمل‌ها

شما در حال نگهداری از جعبه‌ابزار تولید کدِ یک ایستگاه سیگنال هستید. هر ماکرو یک «شابلون» است، یعنی قالبی کوچک که کامپایلر با آن در هر نقطه‌ی فراخوانی، کد تخصص‌یافته تولید می‌کند.

شما فقط چهار ماکرو را می‌نویسید. فایل تست، واژه‌های پوششی کوچکی تعریف می‌کند که ماکروهای شما را فراخوانی می‌کنند (ماکرو را تنها از راه کدی که تولید می‌کند می‌توان مشاهده کرد)، و سپس نتایج زمان اجرای این پوشش‌ها را بررسی می‌کند. پوشش‌ها را همان‌طور که هستند رها کنید.

در هر تمرین، بدنه ورودی‌های لیترالِ نقطه‌ی فراخوانی را روی پشته دریافت می‌کند و باید یک کوتیشن را برجای بگذارد. خطوطِ «کامپایل می‌شود به» کد خطی‌ای را نشان می‌دهند که کوتیشن شما باید تولید کند.

1. دو بار

twice یک کوتیشن لیترال می‌گیرد و کوتیشنی برمی‌گرداند که کوتیشن اصلی را دو بار پشت سر هم اجرا می‌کند.

! : inc-twice ( x -- y ) [ 1 + ] twice ;
! body receives [ 1 + ]  ->  must build [ 1 + 1 + ]
5 inc-twice .   ! => 7

2. تکرار N بار

repeat-quot یک کوتیشن لیترال و یک تعداد لیترال n می‌گیرد و کوتیشنی برمی‌گرداند که کوتیشن اصلی را n بار پشت سر هم اجرا می‌کند. حالت n = 0 کوتیشن خالی [ ] را برمی‌گرداند.

! : inc-thrice ( x -- y ) [ 1 + ] 3 repeat-quot ;
! body receives [ 1 + ] 3  ->  must build [ 1 + 1 + 1 + ]
5 inc-thrice .   ! => 8

3. ترکیب چندگانه

compose-many یک آرایه‌ی لیترال از کوتیشن‌ها می‌گیرد و یک کوتیشن واحد برمی‌گرداند که هرکدام را به نوبت اجرا می‌کند. آرایه‌ی خالی، کوتیشن خالی را برمی‌گرداند.

! : pipeline ( x -- y ) { [ 1 + ] [ 2 * ] [ 1 - ] } compose-many ;
! body receives { [ 1 + ] [ 2 * ] [ 1 - ] }  ->  must build [ 1 + 2 * 1 - ]
5 pipeline .   ! => 11

4. پیمایش بازشده

each-literal یک دنباله‌ی لیترال و یک کوتیشن می‌گیرد. کوتیشنی برمی‌گرداند که برای هر عنصر دنباله، آن عنصر را روی پشته می‌گذارد و سپس کوتیشن را اجرا می‌کند؛ پیمایشی که در زمان کامپایل رخ می‌دهد، بنابراین کد تولیدشده خطی است.

! : sum-three ( -- n ) 0 { 10 20 30 } [ + ] each-literal ;
! body receives { 10 20 30 } [ + ]  ->  must build [ 10 + 20 + 30 + ]
sum-three .   ! => 60

این مورد از میان آن چهار، ساختاری‌ترین است: هم طول دنباله و هم مقادیر عناصر آن، کد تولیدشده را شکل می‌دهند. راهنمایی‌ها گام‌به‌گام نحوه‌ی ساخت آن را توضیح می‌دهند.

ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
Factor Exercism

آماده‌اید شابلونهای علامت را شروع کنید؟

در Exercism ثبت‌نام کنید تا Factor را همراه با 47 مفهوم163 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.