المسارات
/
Factor
Factor
/
التمارين
/
قوالب الإشارات
قوالب الإشارات

قوالب الإشارات

تمرين تعلّمي

مقدمة

أنت تعرف بالفعل كيف تبني اقتباسًا من أجزاء وتشغّله. والاقتباس ليس إلا تسلسلًا من الكود، لذا تنطبق عليه كلمات التسلسل التي تعرفها:

USING: kernel math prettyprint quotations sequences ;

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

تربط append بين اقتباسين، وتُسطّح concat مصفوفة منهما، وتحوّل map البيانات إلى اقتباسات، وتجعل >quotation النتيجة قابلة للاستدعاء. وMACRO: تأخذ هذه المهارة نفسها وتنقلها إلى وقت الترجمة.

ما هو الماكرو

يشبه تعريف MACRO: كلمة عادية، لكن جسمه يُنفَّذ أثناء ترجمة برنامجك، لا أثناء تشغيله. ويجب أن يترك الجسم اقتباسًا واحدًا على المكدس. ثم يُدرج المترجم ذلك الاقتباس في موضع الاستدعاء، أي أنه يستبدل استدعاء الماكرو بالكود الذي أنتجه الجسم. وفي وقت التشغيل لم يبقَ أي ماكرو، بل الكود المولَّد فقط.

USING: kernel macros sequences ;

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

فأينما كتبت [ 1 + ] twice، فإن المترجم:

  1. يشغّل الجسم و[ 1 + ] على المكدس،
  2. يبني dup append الاقتباسَ [ 1 + 1 + ]،
  3. يُدرج كوده، 1 + 1 +، في ذلك الموضع.

لذا فإن كلمة تستدعي twice تُترجم كما لو أنك كتبت 1 + 1 + بيدك:

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

5 inc-twice .   ! => 7

الفرق الجوهري: الكلمة العادية التي تأخذ اقتباسًا تستدعيه في وقت التشغيل عبر call. أما الماكرو فلا يستدعي شيئًا أبدًا، بل يسلّم المترجم كودًا جديدًا ليترجمه في مكانه.

يجب أن تكون المدخلات قيمًا حرفية

لأن الجسم يُنفَّذ في وقت الترجمة، يجب أن تكون مدخلات الماكرو معروفة حينذاك. فيجب أن تكون قيمًا حرفية في موضع الاستدعاء: اقتباس [ … ]، أو عدد، أو مصفوفة، أو سلسلة نصية. أما القيمة التي لا وجود لها إلا في وقت التشغيل فلا يمكن تسليمها إلى ماكرو.

! 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

صندوق الأدوات

جسم الماكرو كود عادي يُرجع اقتباسًا. والكلمات التي التقيتها من قبل تقوم بمعظم العمل:

لبناء… استخدم
[ A B ] من [ A ] و[ B ] append
اقتباس واحد من مصفوفة منها concat >quotation
اقتباس لكل عنصر من عناصر تسلسل map، ثم concat >quotation

وتكتمل العُدّة بكلمتين أخريين من sequences وkernel.

<repetition> (في sequences) تُنشئ تسلسلًا من n نسخة من عنصر واحد. وتأثير المكدس لها هو ( length elt -- repetition )، حيث يأتي العدد أولًا. والنتيجة تسلسل افتراضي، لذا سطّحها كما اعتدت:

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

curry (في kernel) تُضيف قيمة إلى مقدمة اقتباس، فتنتج اقتباسًا جديدًا يدفع تلك القيمة أولًا:

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

وبـcurry تُدمج عنصرًا محددًا في اقتباس، وهي بالضبط ما يحوّل { 10 20 30 } و[ + ] إلى [ 10 + ] [ 20 + ] [ 30 + ].

لماذا هذا أبسط مما يبدو

في بعض اللغات، تحتاج الماكرو إلى صياغة خاصة للاقتباس وإلغاء الاقتباس، وإلى آليات إضافية لتجنّب تعارض الأسماء. أما Factor فلا يحتاج إلى أي من ذلك: فالاقتباس هو أصلًا تسلسل من كلمات محلولة، إذ بحث المحلل عن كل من dup و+ وappend عندما قرأ [ … ]. لذا فجسم الماكرو ليس إلا التعامل مع التسلسلات الذي تجيده بالفعل، والذي يصادف أنه يُرجع اقتباسًا.

قريب وثيق الصلة: $[ … ]

يُرجع الماكرو اقتباسًا ليُدرج في استدعاء. وعندما تريد بدلًا من ذلك قيمة عادية تُحسب في وقت التحليل، فإن $[ 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 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.