lisp - ما الذي يجعل ليسب macros خاص جدا؟




homoiconicity (13)

قراءة مقالات بول جراهام على لغات البرمجة قد يظن المرء أن ليسب macros هي الطريقة الوحيدة للذهاب. كمطور مشغول ، والعمل على منصات أخرى ، لم يكن لدي امتياز استخدام وحدات الماكرو Lisp. بصفتك شخصًا يريد أن يفهم الضجة ، يرجى توضيح ما يجعل هذه الميزة قوية جدًا.

يرجى أيضا ربط هذا بشيء أفهمه من عوالم بايثون ، جافا ، جيم # أو جيم.


Answers

في بيثون لديك مزخرف ، لديك أساسا وظيفة تأخذ وظيفة أخرى كإدخال. يمكنك القيام بما تريده: استدعاء الوظيفة ، أو القيام بشيء آخر ، أو التفاف المكالمة الدالة في أحد الموارد التي يتم الحصول عليها ، وما إلى ذلك ، ولكنك لا تصل إلى نظرة خاطفة داخل هذه الوظيفة. لنقل أننا أردنا أن نجعلها أكثر قوة ، لنفترض أن المشرف الخاص بك قد تلقى رمز الوظيفة كقائمة ، ثم لا يمكنك فقط تنفيذ الوظيفة كما هو ، ولكن يمكنك الآن تنفيذ أجزاء منها ، وإعادة ترتيب خطوط الدالة وما إلى ذلك.


باختصار ، وحدات الماكرو هي تحويلات رمز. أنها تسمح بإدخال العديد من بنيات بناء الجملة الجديدة. على سبيل المثال ، ضع في الاعتبار LINQ في C #. في lisp ، توجد امتدادات لغة مشابهة يتم تنفيذها بواسطة وحدات الماكرو (على سبيل المثال ، إنشاء الحلقة المضمنة ، التكرار). وحدات الماكرو تقلل إلى حد كبير تكرار التعليمات البرمجية. تسمح وحدات الماكرو بتضمين "لغات قليلة" (على سبيل المثال ، حيث في c # / java واحد يمكن استخدام xml لتكوين ، في lisp يمكن تحقيق الشيء نفسه مع وحدات الماكرو). قد تخفي وحدات الماكرو صعوبات في استخدام المكتبات.

على سبيل المثال ، في lisp يمكنك الكتابة

(iter (for (id name) in-clsql-query "select id, name from users" on-database *users-database*)
      (format t "User with ID of ~A has name ~A.~%" id name))

ويخفي هذا كافة قاعدة البيانات (المعاملات ، إغلاق الاتصال الصحيح ، إحضار البيانات ، إلخ) بينما في C # يتطلب إنشاء SqlConnections ، SqlCommands ، إضافة SqlParameters إلى SqlCommands ، حلقات على SqlDataReaders ، إغلاقها بشكل صحيح.


لست متأكدًا من أنني أستطيع إضافة بعض الإحصاءات إلى المشاركات (الممتازة) للجميع ، ولكن ...

تعمل وحدات الماكرو Lisp رائعة بسبب طبيعة تركيب Lisp.

Lisp هي لغة عادية للغاية (فكر في كل شيء هو قائمة ) ؛ تمكّنك وحدات الماكرو من معالجة البيانات والتعليمة البرمجية على النحو نفسه (لا يلزم إجراء تحليل للخيط أو غير ذلك من الاختصارات لتعديل تعبيرات lisp). يمكنك الجمع بين هاتين الميزتين ولديك طريقة نظيفة جدًا لتعديل الشفرة.

تحرير: ما كنت أحاول أن أقوله هو أن Lisp هو homoiconic ، مما يعني أن بنية البيانات لبرنامج lisp مكتوبة في lisp نفسها.

لذلك ، ينتهي الأمر بكيفية إنشاء مولد التعليمات البرمجية الخاص بك أعلى اللغة باستخدام اللغة نفسها بكل قوتها (على سبيل المثال في جافا ، عليك اختراق طريقك باستخدام النسق bytecode ، على الرغم من أن بعض أطر العمل مثل AspectJ تسمح لك القيام بذلك باستخدام نهج مختلف ، هو في الأساس الاختراق).

في الممارسة العملية ، مع وحدات الماكرو ، ينتهي الأمر بك ببناء اللغة المصغرة الخاصة بك على رأس lisp ، دون الحاجة إلى تعلم لغات إضافية أو الأدوات ، ومع استخدام القوة الكاملة للغة نفسها.


بما أن الإجابات الحالية تعطي أمثلة ملموسة جيدة لشرح ما يمكن أن تحققه وحدات الماكرو وكيف ، قد تساعد في جمع بعض الأفكار حول سبب كون المرفق الكلي مكسبًا كبيرًا مقارنة باللغات الأخرى . أولا من هذه الإجابات ، ثم واحدة رائعة من مكان آخر:

... في C ، سيكون عليك كتابة معالج مسبق مخصص [والذي من المحتمل أن يكون مؤهلاً كبرنامج C معقد بما فيه الكفاية ] ...

- Vatine

تحدث إلى أي شخص يتقن C ++ ويسألهم عن المدة التي قضوا فيها في تعلم كل ما يحتاجه القالب من فوضى وهم بحاجة إلى القيام ببرمجة النماذج (التي لا تزال غير قوية).

- مات كيرتس

... في Java ، عليك أن تتعقب طريقك بالنسيج bytecode ، على الرغم من أن بعض أطر العمل مثل AspectJ تسمح لك بالقيام بذلك باستخدام نهج مختلف ، فهو في الأساس اختراق.

ميغيل بينج

DOLIST مشابه ل Perl's foreach أو Python for. أضاف Java نوعًا مشابهًا من بنية الحلقة مع "المحسن" للحلقة في Java 1.5 ، كجزء من JSR-201. لاحظ ما تقوم به وحدات الماكرو الاختلاف. يمكن لمبرمج Lisp الذي يلاحظ وجود نمط شائع في التعليمة البرمجية الخاصة به كتابة ماكرو ليعطي نفسه تجريدًا على مستوى المصدر لهذا النمط. على مبرمج جافا الذي يلاحظ نفس النمط أن يقنع صن بأن هذا التجريد المعين يستحق إضافة إلى اللغة. ومن ثم يتعين على Sun نشر JSR وعقد "مجموعة خبراء" على مستوى الصناعة لتجميع كل شيء. هذه العملية - وفقًا لشركة صن - تستغرق ما معدله 18 شهرًا. بعد ذلك ، يتعين على كتّاب المترجمين الذهاب إلى ترقية مترجماتهم لدعم الميزة الجديدة. وحتى بمجرد أن يدعم المحول البرمجي المفضل لمبرمج جافا الإصدار الجديد من Java ، فمن المحتمل ألا يتمكن '' لا يزال '' من استخدام الميزة الجديدة إلى أن يسمح له بخرق توافق المصدر مع الإصدارات القديمة من Java. لذا فإن الإزعاج الذي يمكن لمبرمجي Lisp المشترك أن يحلوا لأنفسهم في غضون خمس دقائق يبتلي مبرمجين جافا لسنوات.

- بيتر سيبل ، في "اللسانية العملية المشتركة"


لإعطاء إجابة مختصرة ، يتم استخدام وحدات الماكرو لتعريف ملحقات قواعد اللغة إلى لغة اللوتس المشتركة أو لغات محددة النطاق (DSL). يتم تضمين هذه اللغات مباشرة في رمز Lisp الموجود. الآن ، يمكن أن يكون ل DSLs صيغة مشابهة لـ Lisp (مثل مترجم برول Norvig's Prol Interpreter for Common Lisp) أو مختلف تمامًا (مثال: Infix Notation Math for Clojure).

في ما يلي مثال أكثر واقعية:
لدى بايثون قائمة بالفهم مدمجة في اللغة. هذا يعطي بناء جملة بسيط لحالة شائعة. الخط

divisibleByTwo = [x for x in range(10) if x % 2 == 0]

ينتج قائمة تحتوي على جميع الأرقام الزوجية بين 0 و 9. في بيثون 1.5 يوم لم يكن هناك مثل هذا النحو ؛ ستستخدم شيئًا كهذا:

divisibleByTwo = []
for x in range( 10 ):
   if x % 2 == 0:
      divisibleByTwo.append( x )

كلاهما مكافئ وظيفيا. دعونا نستحضر تعليقنا لعدم التصديق ونتظاهر بأن لسب لديها ماكرو متكرر للغاية لا يفعل سوى تكرار ولا توجد طريقة سهلة للقيام بما يعادل فهم القائمة.

في Lisp يمكنك كتابة ما يلي. يجب أن أشير إلى أن هذا المثال المفتعل قد تم اختياره ليكون متطابقًا مع شفرة Python وليس مثالًا جيدًا على شفرة Lisp.

;; the following two functions just make equivalent of Python's range function
;; you can safely ignore them unless you are running this code
(defun range-helper (x)
  (if (= x 0)
      (list x)
      (cons x (range-helper (- x 1)))))

(defun range (x)
  (reverse (range-helper (- x 1))))

;; equivalent to the python example:
;; define a variable
(defvar divisibleByTwo nil)

;; loop from 0 upto and including 9
(loop for x in (range 10)
   ;; test for divisibility by two
   if (= (mod x 2) 0) 
   ;; append to the list
   do (setq divisibleByTwo (append divisibleByTwo (list x))))

قبل أن أذهب إلى أبعد من ذلك ، يجب أن أشرح بشكل أفضل ما هو الماكرو. إنه تحويل يتم على الكود بواسطة الكود. أي ، قطعة من الكود ، يقرأها المترجم (أو المترجم) ، الذي يأخذ في الكود كوسيطة ، يعالج ويعيد النتيجة ، ثم يتم تشغيلها في المكان.

بالطبع هذا كثير من الكتابة والمبرمجين كسالى. لذا يمكننا تعريف DSL للقيام بفهم القوائم. في الواقع ، نحن نستخدم ماكروًا واحدًا بالفعل (الماكرو الحلقي).

يعرّف Lisp بعض نماذج التركيب الخاصة. الاقتباس ( ' ) يشير إلى الرمز التالي هو حرفي. يشير quasiquote أو backtick ( ` ) إلى الرمز المميز التالي هو حرفي مع الهروب. يشار إلى الهروب من قبل عامل الفاصلة. الحرف '(1 2 3) هو ما يعادل بايثون [1, 2, 3] . يمكنك تخصيصه لمتغير آخر أو استخدامه في مكانه. يمكنك التفكير في `(1 2 ,x) كمكافئ لـ Python's [1, 2, x] حيث x هو متغير تم تعريفه مسبقًا. تدوين هذه القائمة جزء من السحر الذي ينتقل إلى وحدات الماكرو. الجزء الثاني هو قارئ Lisp الذي يحل بذكاء وحدات الماكرو ليحل محل الشفرة ولكن أفضل ما هو موضح أدناه:

حتى نتمكن من تحديد ماكرو يسمى lcomp (اختصار لفهم القائمة). سيكون تركيب الجملة بالضبط مثل python الذي استخدمناه في المثال [x for x in range(10) if x % 2 == 0] - (lcomp x for x in (range 10) if (= (% x 2) 0))

(defmacro lcomp (expression for var in list conditional conditional-test)
  ;; create a unique variable name for the result
  (let ((result (gensym)))
    ;; the arguments are really code so we can substitute them 
    ;; store nil in the unique variable name generated above
    `(let ((,result nil))
       ;; var is a variable name
       ;; list is the list literal we are suppose to iterate over
       (loop for ,var in ,list
            ;; conditional is if or unless
            ;; conditional-test is (= (mod x 2) 0) in our examples
            ,conditional ,conditional-test
            ;; and this is the action from the earlier lisp example
            ;; result = result + [x] in python
            do (setq ,result (append ,result (list ,expression))))
           ;; return the result 
       ,result)))

الآن يمكننا تنفيذ الأمر في سطر الأوامر:

CL-USER> (lcomp x for x in (range 10) if (= (mod x 2) 0))
(0 2 4 6 8)

أنيق جدا ، هاه؟ الآن لا تتوقف عند هذا الحد. لديك آلية ، أو فرشاة للرسم ، إذا أردت. يمكنك الحصول على أي صيغة قد تحتاجها. مثل بايثون أو سي # with بناء الجملة. أو بناء جملة LINQ .NET. في النهاية ، هذا ما يجذب الناس إلى Lisp - المرونة القصوى.


تمثل وحدات ماكرو Lisp نمطًا يحدث في أي مشروع برمجة كبير تقريبًا. في نهاية المطاف في برنامج كبير لديك قسم معين من التعليمات البرمجية حيث تدرك أنه سيكون من الأسهل والأقل عرضة للخطأ لكتابة البرنامج الذي يخرج رمز المصدر كنص والذي يمكنك فقط لصق في.

في بايثون الكائنات لها طريقتان __repr__ و __str__ . __str__ هو ببساطة تمثيل الإنسان المقروء. __repr__ تُرجع تمثيلًا يمثل رمز Python صالحًا ، وهو ما يمكن إدخاله في المترجم على أنه بايثون صالح. بهذه الطريقة ، يمكنك إنشاء قصاصات صغيرة من Python تُنشئ شفرة صالحة يمكن لصقها في مصدرك الفعلي.

في Lisp تمت إضفاء الطابع الرسمي على هذه العملية برمتها من خلال النظام الكلي. من المؤكد أنها تمكنك من إنشاء إضافات للبناء والقيام بكل أنواع الأشياء الفاخرة ، ولكن يتم تلخيص الفائدة الفعلية عن طريق ما سبق. بالطبع أنه يساعد على أن نظام الماكرو Lisp يتيح لك التعامل مع هذه "القصاصات" مع القوة الكاملة للغة بأكملها.


في حين أن كل ما سبق يشرح ما هي وحدات الماكرو ولديها أمثلة رائعة ، أعتقد أن الفرق الرئيسي بين الماكرو والوظيفة العادية هو أن LISP تقوم بتقييم جميع المعلمات أولاً قبل استدعاء الدالة. مع الماكرو هو عكس ذلك ، يمرر LISP المعلمات غير مترجم إلى الماكرو. على سبيل المثال ، إذا قمت بتمرير (+ 1 2) إلى دالة ، فستتلقى الدالة القيمة 3. إذا قمت بتمرير هذا إلى ماكرو ، فستتلقى قائمة (+ 1 2). هذا يمكن استخدامه للقيام بكل أنواع الأشياء المفيدة بشكل لا يصدق.

  • إضافة بنية تحكم جديدة ، على سبيل المثال حلقة أو تفكيك قائمة
  • قياس الوقت المستغرق لتنفيذ وظيفة تم تمريرها. مع وظيفة سيتم تقييم المعلمة قبل أن يتم تمرير التحكم إلى الوظيفة. باستخدام الماكرو ، يمكنك لصق الشفرة بين بداية ساعة التوقف وإيقافها. يحتوي أدناه على نفس التعليمة البرمجية نفسها في ماكرو ودالة ويكون الإخراج مختلفًا جدًا. ملاحظة: هذا مثال مفتعل وتم اختيار التنفيذ بحيث يكون متطابقًا لإبراز الفرق بشكل أفضل.

    (defmacro working-timer (b) 
      (let (
            (start (get-universal-time))
            (result (eval b))) ;; not splicing here to keep stuff simple
        ((- (get-universal-time) start))))
    
    (defun my-broken-timer (b)
      (let (
            (start (get-universal-time))
            (result (eval b)))    ;; doesn't even need eval
        ((- (get-universal-time) start))))
    
    (working-timer (sleep 10)) => 10
    
    (broken-timer (sleep 10)) => 0
    


حصلت على هذا من كتاب الطبخ المشترك المعتدل لكنني أعتقد أنه يفسر لماذا ماكروس الليمون جيدة بطريقة لطيفة.

"إن الماكرو هو جزء عادي من كود Lisp الذي يعمل على جزء آخر من شفرة Lisp المفترضة ، ويرجمها إلى (نسخة أقرب إلى) Lisp القابل للتنفيذ. قد يبدو ذلك معقدًا نوعًا ما ، لذا دعنا نقدم مثالًا بسيطًا. لنفترض أنك تريد إصدار setq الذي يحدد متغيرين إلى نفس القيمة ، لذا إذا كتبت

(setq2 x y (+ z 3))

عندما يتم ضبط z=8 كل من x و y على 11. (لا أستطيع التفكير في أي استخدام لهذا ، ولكنه مجرد مثال).

يجب أن يكون واضحًا أننا لا نستطيع تعريف setq2 كدالة. إذا كانت x=50 و y=-5 ، ستتلقى هذه الدالة القيم 50 و -5 و 11 ؛ لن يكون لديها معرفة بالمتغيرات التي من المفترض أن يتم ضبطها. ما نريد أن نقوله حقاً هو ، عندما ترى (نظام Lisp) (setq2 v1 v2 e) ، تعامله على أنه مكافئ لـ (progn (setq v1 e) (setq v2 e)) . في الواقع ، هذا ليس صحيحًا تمامًا ، لكنه سيفعل الآن. يسمح لنا الماكرو بالقيام بذلك على وجه التحديد ، من خلال تحديد برنامج لتحويل نمط الإدخال (setq2 v1 v2 e) "إلى نمط الإخراج (progn ...) ."

إذا كنت تعتقد أن هذا أمر رائع ، يمكنك الاستمرار في القراءة هنا: http://cl-cookbook.sourceforge.net/macros.html


تتيح لك وحدات الماكرو Lisp تحديد متى سيتم تقييم أي جزء أو تعبير (إذا حدث). لوضع مثال بسيط ، فكر في C:

expr1 && expr2 && expr3 ...

ما يقوله هذا: قيّم expr1 ، و إذا كان صحيحًا ، expr2 ، وما إلى ذلك.

حاول الآن جعل هذا && في وظيفة ... هذا صحيح ، لا يمكنك ذلك. الاتصال بشيء مثل:

and(expr1, expr2, expr3)

سيتم تقييم جميع exprs الثلاثة قبل تقديم إجابة بغض النظر عما إذا كان expr1 كاذبة!

مع وحدات الماكرو lisp يمكنك رمز شيء مثل:

(defmacro && (expr1 &rest exprs)
    `(if ,expr1                     ;` Warning: I have not tested
         (&& ,@exprs)               ;   this and might be wrong!
         nil))

الآن لديك && ، والتي يمكنك الاتصال بها تمامًا مثل وظيفة ، ولن تقوم بتقييم أي نماذج تمر عليها ما لم تكن كلها صحيحة.

لمعرفة كيف يكون ذلك مفيدًا ، يتباين التباين:

(&& (very-cheap-operation)
    (very-expensive-operation)
    (operation-with-serious-side-effects))

و:

and(very_cheap_operation(),
    very_expensive_operation(),
    operation_with_serious_side_effects());

الأشياء الأخرى التي يمكنك القيام بها مع وحدات الماكرو هي إنشاء كلمات رئيسية جديدة و / أو لغات مصغرة (تحقق من الماكرو (loop ...) على سبيل المثال) ، ودمج لغات أخرى في lisp ، على سبيل المثال ، يمكنك كتابة ماكرو يتيح لك قل شيئًا مثل:

(setvar *rows* (sql select count(*)
                      from some-table
                     where column1 = "Yes"
                       and column2 like "some%string%")

وليس حتى الوصول إلى وحدات الماكرو القارئ .

أتمنى أن يساعدك هذا.


يأخذ ماكرو lisp جزء برنامج كمدخل. يمثل جزء البرنامج هذا بنية بيانات يمكن التلاعب بها وتحويلها بالطريقة التي تريدها. في النهاية يخرج الماكرو جزء برنامج آخر ، وهذا الجزء هو ما يتم تنفيذه في وقت التشغيل.

لا يحتوي C # على وحدة ماكرو ، ومع ذلك سيكون مكافئًا إذا قام المحلل اللغوي بتحليل الشفرة إلى شجرة CodeDOM ، وتمرير ذلك إلى طريقة ، والتي حولت ذلك إلى CodeDOM آخر ، والذي تم تجميعه بعد ذلك في IL.

يمكن استخدام هذا لتطبيق بناء الجملة "للسكر" مثل for each جملة using -clause و linq select -expressions وما إلى ذلك ، مثل وحدات الماكرو التي تتحول إلى الكود الأساسي.

إذا كانت Java تحتوي على وحدات ماكرو ، يمكنك تنفيذ بناء جملة Linq في Java ، دون الحاجة إلى Sun لتغيير اللغة الأساسية.

فيما يلي التعليمة البرمجية الزائفة لكيفية ظهور ماكرو نمط lisp في C # لتطبيق using :

define macro "using":
    using ($type $varname = $expression) $block
into:
    $type $varname;
    try {
       $varname = $expression;
       $block;
    } finally {
       $varname.Dispose();
    }

توسع وحدات الماكرو Lisp الشائعة بشكل أساسي "الأوليات الأولية" من شفرتك.

على سبيل المثال ، في C ، تعمل بنية التبديل / الحالة فقط مع أنواع متكاملة وإذا كنت تريد استخدامها للعوامات أو السلاسل ، فيتم تركك مع عبارات متداخلة ومقارنة صريحة. كما لا توجد طريقة يمكنك كتابة ماكرو C للقيام بهذه المهمة نيابة عنك.

ولكن ، بما أن الماكرو التافه (بشكل أساسي) هو برنامج lisp يأخذ مقتطفات من الكود كمدخل ويعيد الكود ليحل محل "استدعاء" الماكرو ، يمكنك تمديد ذخيرة "primitives" الخاصة بك بقدر ما تريد ، وعادة ما تنتهي مع برنامج أكثر قابلية للقراءة.

لفعل الشيء نفسه في C ، يجب عليك كتابة معالج مسبق مخصص يأكل مصدرك الأولي (ليس تماماً C) ويبصق شيئاً يمكن لمجمّع C أن يفهمه. انها ليست طريقة خاطئة للذهاب ، ولكن ليس بالضرورة أسهل.


تقييم كسول يجعل وحدات الماكرو زائدة عن الحاجة

هذا هراء محض (ليس خطأك ؛ لقد سمعت به من قبل). صحيح أنه يمكنك استخدام وحدات الماكرو لتغيير ترتيب التعبير والسياق وما إلى ذلك من تقييم التعبير ، ولكن هذا هو الاستخدام الأساسي للماكرو ، وليس من الملائم حقاً محاكاة لغة كسولة باستخدام وحدات الماكرو المخصصة بدلاً من الوظائف. لذلك إذا كنت قد وصلت إلى وحدات الماكرو من هذا الاتجاه ، فستكون في الواقع محبطًا.

وحدات الماكرو هي لتوسيع اللغة مع الأشكال النحوية الجديدة. بعض القدرات المحددة من وحدات الماكرو هي

  1. التأثير على الترتيب والسياق وغير ذلك من تقييم التعبير.
  2. إنشاء نماذج ربط جديدة (أي تؤثر على النطاق الذي يتم تقييم تعبير فيه).
  3. أداء حساب وقت التحويل ، بما في ذلك تحليل الكود والتحويل.

يمكن أن تكون وحدات الماكرو (1) بسيطة جدًا. على سبيل المثال ، في Racket ، نموذج التعامل مع الاستثناء ، with-handlers ، هو مجرد ماكرو يتوسع في call-with-exception-handler ، وبعض الشروط الشرطية ، وبعض كود الاستمرارية. يتم استخدامه على هذا النحو:

(with-handlers ([(lambda (e) (exn:fail:network? e))
                 (lambda (e)
                   (printf "network seems to be broken\n")
                   (cleanup))])
  (do-some-network-stuff))

يطبق الماكرو مفهوم "جمل predicate-and-handler في سياق ديناميكي الاستثناء" استناداً إلى call-with-exception-handler بدائي call-with-exception-handler الذي يعالج كافة الاستثناءات عند نقطة أنها رفعت.

الاستخدام الأكثر تعقيدًا للماكروات هو تنفيذ محلل محلل LALR (1) . بدلاً من ملف منفصل يحتاج إلى معالجة مسبقة ، شكل parser هو نوع آخر من التعبير. يستغرقه وصف النحو ، يحسب الجداول في وقت الترجمة ، وينتج دالة محلل. يتم تحديد إجراءات الإجراء بشكل تفاعلي ، بحيث يمكن أن تشير إلى تعريفات أخرى في الملف أو حتى المتغيرات التي تعتمد على lambda . يمكنك حتى استخدام امتدادات لغة أخرى في إجراءات الإجراء.

في نهاية المدقع ، هو مكتوب Racket لهجة مكتوبة من Racket تنفيذها عبر وحدات الماكرو. ولديها نظام متطور من نوع مصمم لمطابقة تعابير Racket / Scheme code ، كما أنه يتفاعل مع الوحدات غير المصنفة من خلال حماية الوظائف المكتوبة بعقود برمجية ديناميكية (يتم تنفيذها أيضًا عبر وحدات الماكرو). يتم تنفيذه من خلال وحدة ماكرو "وحدة نمطية مطبوعة" يتم توسيعها ، والتحقق من نوعها ، وتحويل نص الوحدة ، بالإضافة إلى وحدات الماكرو المساعدة لتوصيل معلومات النوع بالتعاريف ، وما إلى ذلك.

FWIW ، هناك أيضا Lazy Racket ، لهجة كسول من Racket. لا يتم تنفيذه عن طريق تحويل كل وظيفة إلى ماكرو ، ولكن عن طريق إعادة كتابة lambda ، define ، وبناء جملة الدالة الوظيفية إلى وحدات الماكرو التي تنشئ وتُجبِر الوعود.

باختصار ، يحتوي التقييم البطيئ ووحدات الماكرو على نقطة صغيرة من التقاطع ، ولكنها أشياء مختلفة للغاية. ومن المؤكد لا يتم تضمين وحدات الماكرو من خلال تقييم كسول.