ماكرو صحي
في علوم الحاسوب ، تُعرف وحدات الماكرو الصحية بأنها وحدات ماكرو يضمن توسيعها عدم التسبب في التقاط المعرفات عن طريق الخطأ . وهي سمة من سمات لغات البرمجة مثل Scheme [ 1 ] و Dylan [ 2 ] و Rust و Nim و Julia . كانت مشكلة الالتقاط العرضي معروفة جيدًا في مجتمع Lisp قبل ظهور وحدات الماكرو الصحية. كان مطورو وحدات الماكرو يستخدمون خصائص لغوية لتوليد معرفات فريدة (مثل gensym) أو يستخدمون معرفات مُشفرة لتجنب هذه المشكلة. تُعد وحدات الماكرو الصحية حلاً برمجيًا لمشكلة الالتقاط، وهي مُدمجة في مُوسِّع الماكرو. وقد صِيغ مصطلح "الصحّة" في ورقة بحثية لكولبيكر وآخرون عام 1986، والتي قدمت توسيع وحدات الماكرو الصحية، مُستوحاة من المصطلحات المُستخدمة في الرياضيات. [ 3 ]
مشكلة النظافة
تظليل متغير
في لغات البرمجة التي تعتمد على أنظمة ماكرو غير مُنظَّمة، قد تُخفى روابط المتغيرات الموجودة عن الماكرو بواسطة روابط متغيرات تُنشأ أثناء توسيعه. في لغة C ، يمكن توضيح هذه المشكلة من خلال المقطع البرمجي التالي:
#define INCI(i) { int a=0; ++i; } int main ( void ) { int a = 4 , b = 8 ; INCI ( a ); INCI ( b ); printf ( "a is now %d, b is now %d \n " , a , b ); return 0 ; }ينتج عن تشغيل ما سبق من خلال معالج C المسبق ما يلي :
int main ( void ) { int a = 4 , b = 8 ; { int a = 0 ; ++ a ; }; { int a = 0 ; ++ b ; }; printf ( "a is now %d, b is now %d \n " , a , b ); return 0 ; }يتم إخفاء المتغير aالمُعلن عنه في النطاق الرئيسي بواسطة المتغير الموجود في الماكرو، مما يُنشئ نطاقًا جديدًا . ونتيجةً لذلك، لا يتغير هذا المتغير أبدًا عند تنفيذ البرنامج، كما يُظهر ناتج البرنامج المُترجم.aa
أصبح a الآن 4، وأصبح b الآن 9
إعادة تعريف وظائف المكتبة القياسية
قد تتجاوز مشكلة النظافة مجرد ربط المتغيرات. لنأخذ مثالاً على ذلك ماكرو Common Lisp التالي:
( defmacro my-unless ( condition &body body ) ` ( if ( not , condition ) ( progn ,@ body )))على الرغم من عدم وجود إشارات إلى متغيرات في هذا الماكرو، فإنه يفترض أن الرموز "if" و"not" و"progn" مرتبطة بتعريفاتها المعتادة في المكتبة القياسية. مع ذلك، إذا تم استخدام الماكرو المذكور أعلاه في الكود التالي:
( flet (( not ( x ) x )) ( my-unless t ( format t "لا يجب طباعة هذا!" )))تم تغيير تعريف "لا" محلياً، وبالتالي توسع نطاق my-unlessالتغييرات.
مع ذلك، تجدر الإشارة إلى أن هذا السلوك ممنوع في لغة Common Lisp، وفقًا للبند 11.1.2.1.2 من قيود حزمة COMMON-LISP للبرامج المتوافقة . ومع ذلك، من الممكن إعادة تعريف الدوال بالكامل. توفر بعض تطبيقات Common Lisp ميزة قفل الحزم لمنع المستخدم من تغيير التعريفات في الحزم عن طريق الخطأ.
إعادة تعريف الدالة المعرفة بواسطة البرنامج
بالطبع، يمكن أن تحدث المشكلة بالنسبة للوظائف المعرفة بواسطة البرنامج بطريقة مماثلة:
( defun user-defined-operator ( cond ) ( not cond ))( defmacro my-unless ( condition &body body ) ` ( if ( user-defined-operator , condition ) ( progn ,@ body )))؛ ... لاحقاً ...( flet (( user-defined-operator ( x ) x )) ( my-unless t ( format t "لا يجب طباعة هذا!" )))يقوم موقع الاستخدام بإعادة تعريف user-defined-operatorسلوك الماكرو وبالتالي تغييره.
الاستراتيجيات المستخدمة في اللغات التي تفتقر إلى وحدات الماكرو الصحية
يمكن حل مشكلة النظافة باستخدام المغذيات الكبيرة التقليدية من خلال العديد من الحلول البديلة.
التعتيم
أبسط حل، إذا كانت هناك حاجة إلى تخزين مؤقت أثناء توسيع الماكرو، هو استخدام أسماء متغيرات غير عادية في الماكرو على أمل ألا يتم استخدام نفس الأسماء من قبل بقية البرنامج.
#define INCI(i) { int INCIa = 0; ++i; } int main ( void ) { int a = 4 , b = 8 ; INCI ( a ); INCI ( b ); printf ( "a is now %d, b is now %d \n " , a , b ); return 0 ; }INCIaإلى حين إنشاء متغير باسم معين ، ينتج هذا الحل المخرجات الصحيحة:
أصبح a الآن 5، وأصبح b الآن 9
تم حل المشكلة للبرنامج الحالي، لكن هذا الحل غير موثوق. يجب على المبرمج الحفاظ على تزامن المتغيرات المستخدمة داخل الماكرو مع تلك المستخدمة في بقية البرنامج. تحديدًا، سيؤدي استخدام الماكرو INCIعلى متغير INCIaإلى فشل العملية بنفس الطريقة التي فشل بها الماكرو الأصلي على متغير آخر a.
إنشاء رمز مؤقت
في بعض لغات البرمجة، يُمكن إنشاء اسم متغير جديد، أو رمز، وربطه بموقع مؤقت. يضمن نظام معالجة اللغة عدم تعارض هذا الاسم مع أي اسم أو موقع آخر في بيئة التنفيذ. تقع مسؤولية اختيار استخدام هذه الميزة ضمن تعريف الماكرو على عاتق المبرمج. استُخدمت هذه الطريقة في لغة MacLisp ، حيث يُمكن استخدام دالة مُسماة gensymلإنشاء اسم رمز جديد. توجد دوال مُشابهة (عادةً ما تحمل نفس الاسم ) في العديد من اللغات الشبيهة بلغة Lisp، بما في ذلك معيار Common Lispgensym المُطبق على نطاق واسع [ 4 ] ولغة Elisp .
على الرغم من أن إنشاء الرموز يحل مشكلة تظليل المتغيرات، إلا أنه لا يحل مشكلة إعادة تعريف الدوال بشكل مباشر. [ 5 ] ومع ذلك، gensymفإن إمكانيات الماكرو ووظائف المكتبة القياسية كافية لتضمين وحدات ماكرو صحية في لغة غير صحية. [ 6 ]
رمز غير مُخزَّن في وقت القراءة
يشبه هذا التمويه من حيث أن اسمًا واحدًا يُستخدم في عدة توسيعات لنفس الماكرو. ولكن على عكس الاسم غير المألوف، يُستخدم رمز غير مُدرج في وقت القراءة (يُشار إليه بالرمز #:)، والذي يستحيل وجوده خارج الماكرو، على غرار gensym.
الطرود
باستخدام حزم مثل تلك الموجودة في لغة Common Lisp، يستخدم الماكرو ببساطة رمزًا خاصًا من الحزمة التي تم تعريفه فيها. لن يظهر هذا الرمز عرضيًا في كود المستخدم. سيتعين على كود المستخدم الوصول إلى داخل الحزمة باستخدام ::رمز النقطتين الرأسيتين () لمنح نفسه الإذن باستخدام الرمز الخاص، على سبيل المثال cool-macros::secret-sym. عند هذه النقطة، تصبح مسألة النقص العرضي في نظافة الماكرو غير ذات صلة. علاوة على ذلك، يصنف معيار ANSI Common Lisp إعادة تعريف الدوال والمعاملات القياسية، سواء على مستوى النظام أو على المستوى المحلي، على أنها استدعاء لسلوك غير مُعرَّف . وبالتالي، يمكن تشخيص هذا الاستخدام من قِبل التطبيق على أنه خاطئ. وهكذا، يوفر نظام حزم Lisp حلاً عمليًا وكاملاً لمشكلة نظافة الماكرو، والتي يمكن اعتبارها مثالاً على تضارب الأسماء.
على سبيل المثال، في مثال إعادة تعريف الدالة المُعرَّفة في البرنامج ، my-unlessيمكن أن يوجد الماكرو في حزمة خاصة به، حيث user-defined-operatorيُمثِّل رمزًا خاصًا في تلك الحزمة. وبالتالي، سيكون الرمز user-defined-operatorالذي يظهر في كود المستخدم رمزًا مختلفًا، لا علاقة له بالرمز المستخدم في تعريف الماكرو my-unless.
الأشياء المادية
في بعض اللغات، لا يشترط أن يتطابق توسيع الماكرو مع الكود النصي؛ فبدلاً من التوسيع إلى تعبير يحتوي على الرمز f، قد ينتج عن الماكرو توسيع يحتوي على الكائن الفعلي المشار إليه بواسطة الرمز f. وبالمثل، إذا احتاج الماكرو إلى استخدام متغيرات محلية أو كائنات مُعرَّفة في حزمة الماكرو، فيمكنه التوسيع إلى استدعاء كائن إغلاق تكون بيئته المعجمية المحيطة هي بيئة تعريف الماكرو.
التحول الصحي
تستخدم أنظمة الماكرو الصحية في لغات مثل Scheme عملية توسيع للماكرو تحافظ على النطاق المعجمي لجميع المعرفات وتمنع الاستحواذ العرضي. تُسمى هذه الخاصية بالشفافية المرجعية . في الحالات التي يُراد فيها الاستحواذ، تسمح بعض الأنظمة للمبرمج بانتهاك آليات النظافة الخاصة بنظام الماكرو بشكل صريح.
على سبيل المثال، أنظمة إنشاء let-syntaxوحدات define-syntaxالماكرو في لغة Scheme تتميز بالنظام الصحي، لذا فإن تطبيق Scheme التالي my-unlessسيحقق السلوك المطلوب:
( define-syntax my-unless ( syntax-rules () (( _ condition body ... ) ( if ( not condition ) ( begin body ... )))))( let (( not ( lambda ( x ) x ))) ( my-unless #t ( display "لا يجب طباعة هذا!" ) ( newline )))يقوم معالج الماكرو الصحي، المسؤول عن تحويل أنماط نموذج الإدخال إلى نموذج الإخراج، بالكشف عن تعارضات الرموز وحلها عن طريق تغيير أسماء الرموز مؤقتًا. وتتمثل الاستراتيجية الأساسية في تحديد الروابط في تعريف الماكرو واستبدال تلك الأسماء برموز عامة، وتحديد المتغيرات الحرة في تعريف الماكرو والتأكد من البحث عن تلك الأسماء في نطاق تعريف الماكرو نفسه وليس في النطاق الذي استُخدم فيه الماكرو.
التطبيقات
نشأت أنظمة الماكرو التي تُطبّق النظافة تلقائيًا مع لغة Scheme. قدّم كولبيكر خوارزمية KFFD الأصلية لنظام ماكرو صحي في عام 1986. [ 3 ] في ذلك الوقت، لم يكن هناك نظام ماكرو قياسي مُعتمد في تطبيقات Scheme. بعد ذلك بوقت قصير، في عام 1987، اقترح كولبيكر وواند لغةً تصريحيةً قائمةً على الأنماط لكتابة وحدات الماكرو، والتي كانت بمثابة النواة الأولى syntax-rulesلخاصية الماكرو المُعتمدة في معيار R5RS. [ 1 ] [ 7 ] في عام 1988، اقترح باودن وريس الإغلاقات التركيبية، وهي آلية نظافة بديلة، كبديل لنظام كولبيكر وآخرين. [ 8 ] على عكس خوارزمية KFFD، تتطلب الإغلاقات التركيبية من المبرمج تحديد نطاق المُعرّف بشكل صريح. في عام 1993، قدّم ديبفيج وآخرون syntax-caseنظام الماكرو، الذي يستخدم تمثيلًا بديلًا للتركيب ويحافظ على النظافة تلقائيًا. [ 9 ]syntax-case يمكن للنظام التعبير عن لغة syntax-rulesالأنماط كوحدة ماكرو مشتقة. قد يكون مصطلح " نظام الماكرو" غامضًا لأنه، في سياق لغة Scheme، يمكن أن يشير إلى كل من بنية مطابقة الأنماط (مثل قواعد بناء الجملة) وإطار عمل لتمثيل ومعالجة بناء الجملة (مثل حالة بناء الجملة، والإغلاقات النحوية).
قواعد بناء الجملة
قواعد بناء الجملة هي أداة متقدمة لمطابقة الأنماط ، تهدف إلى تسهيل كتابة وحدات الماكرو. مع ذلك، syntax-rulesفهي لا تستطيع وصف بعض أنواع وحدات الماكرو بإيجاز، كما أنها غير كافية للتعبير عن أنظمة ماكرو أخرى. وُصفت قواعد بناء الجملة في ملحق وثيقة R4RS، ولكن لم تكن إلزامية. لاحقًا، اعتمدتها R5RS كأداة ماكرو قياسية. إليك مثال على syntax-rulesماكرو يُبدّل قيمة متغيرين:
( تعريف-بناء-الصيغة تبديل! ( قواعد-بناء-الصيغة () (( _ a b ) ( دع (( مؤقت a )) ( تعيين! a b ) ( تعيين! b مؤقت )))))حالة بناء الجملة
نظراً لقصور syntax-rulesنظام الماكرو القائم كلياً على الصيغة، اعتمد معيار R6RS Scheme نظام الماكرو القائم على الصيغة. [ 10 ] على عكس ذلك syntax-rules، syntax-caseيحتوي هذا النظام على لغة مطابقة أنماط وآلية منخفضة المستوى لكتابة الماكرو. تسمح الأولى بكتابة الماكرو بشكل تصريحي، بينما تسمح الثانية بتنفيذ واجهات أمامية بديلة لكتابة الماكرو. مثال التبديل المذكور سابقاً مطابق تقريباً في هذا النظام syntax-caseلأن لغة مطابقة الأنماط متشابهة.
( define-syntax swap! ( lambda ( stx ) ( syntax-case stx () (( _ a b ) ( syntax ( let (( temp a )) ( set! a b ) ( set! b temp ))))))))مع ذلك، syntax-caseتُعدّ هذه الطريقة أقوى من قواعد بناء الجملة. على سبيل المثال، syntax-caseيمكن للماكرو تحديد شروط جانبية لقواعد مطابقة الأنماط الخاصة بها عبر دوال Scheme اختيارية. بدلاً من ذلك، يمكن لكاتب الماكرو اختيار عدم استخدام واجهة مطابقة الأنماط ومعالجة بناء الجملة مباشرةً. باستخدام هذه datum->syntaxالدالة، يمكن لماكرو حالة بناء الجملة أيضًا التقاط المعرّفات عمدًا، مما يُخلّ بمعايير النظافة البرمجية.
أنظمة أخرى
تم اقتراح وتطبيق أنظمة ماكرو أخرى للغة Scheme. تُعدّ الإغلاقات التركيبية وإعادة التسمية الصريحة [ 11 ] نظامين بديلين للماكرو. كلا النظامين أدنى مستوى من قواعد التركيب، ويتركان مسؤولية تطبيق قواعد النظافة البرمجية لمُنشئ الماكرو. وهذا يختلف عن قواعد التركيب وحالة التركيب، اللذين يفرضان النظافة البرمجية تلقائيًا بشكل افتراضي. تُعرض هنا أمثلة التبديل المذكورة أعلاه باستخدام الإغلاق التركيبي وتطبيق إعادة التسمية الصريحة على التوالي.
;; الإغلاقات التركيبية ( تعريف-تركيب تبديل! ( محول-ماكرو-sc ( لامدا ( نموذج بيئة ) ( دع (( أ ( إغلاق-تركيب ( cadr نموذج ) بيئة )) ( ب ( إغلاق-تركيب ( caddr نموذج ) بيئة )))) ` ( دع (( مؤقت ، أ )) ( تعيين! ، أ ، ب ) ( تعيين! ، ب مؤقت ))))));; إعادة تسمية صريحة ( define-syntax swap! ( er-macro-transformer ( lambda ( form rename compare ) ( let (( a ( cadr form )) ( b ( caddr form )) ( temp ( rename 'temp ))) ` ( , ( rename 'let ) (( , temp , a )) ( , ( rename 'set! ) , a , b ) ( , ( rename 'set! ) , b , temp ))))))اللغات ذات الأنظمة الكلية الصحية
- مخطط – قواعد بناء الجملة، وحالة بناء الجملة، والإغلاقات النحوية، وغيرها.
- Racket – وهو نوع من أنواع Scheme، كان نظام الماكرو الخاص به يعتمد في الأصل على حالة بناء الجملة، ولكنه الآن يحتوي على المزيد من الميزات.
- نيميرل [ 12 ]
- ديلان
- إكسير [ 13 ]
- نيم
- الصدأ
- هاكس
- Mary2 – أجسام ماكرو ذات نطاق محدد في لغة مشتقة من ALGOL 68 حوالي عام 1978
- جوليا [ 14 ]
- راكو – يدعم كلاً من وحدات الماكرو الصحية وغير الصحية [ 15 ]
- لين [ 16 ]
نقد
توفر وحدات الماكرو الصحية الأمان والشفافية المرجعية على حساب جعل عملية التقاط المتغيرات المقصودة أقل وضوحًا. يكتب دوغ هويت، مؤلف كتاب Let Over Lambda : [ 17 ]
معظم الطرق المُتبعة للحد من تأثير التقاط المتغيرات لا تُؤدي إلا إلى تقليل إمكانيات استخدام defmacro. تُعدّ وحدات الماكرو الصحية، في أفضل الأحوال، بمثابة حاجز أمان للمبتدئين؛ أما في أسوأ الأحوال، فتُشكّل سياجًا كهربائيًا، يحاصر ضحاياها في سجن مُعقّم وآمن من الالتقاط.
— دوغ هويت
تُتيح العديد من أنظمة الماكرو الصحية إمكانية الخروج من المأزق دون المساس بضمانات النظافة؛ فعلى سبيل المثال، يسمح لك Racket بتحديد معلمات بناء الجملة ، مما يُتيح لك إدخال متغيرات مُقيدة بشكل انتقائي. ويُقدم جريج هندرسشوت مثالًا في كتابه Fear of Macros [ 18 ] على تنفيذ عامل if الإشاري بهذه الطريقة.
انظر أيضاً
ملحوظات
- 1 2 كيلسي، ريتشارد؛ كلينجر، ويليام؛ ريس، جوناثان؛ وآخرون . (أغسطس 1998). "التقرير الخامس المنقح حول مخطط لغة الخوارزميات" . الحوسبة الرمزية والحسابية من الرتبة العليا . 11 (1): 7-105 . doi : 10.1023/A:1010051815785 .
- ↑ فينبرغ، ن.؛ كين، إس إي؛ ماثيوز، آر أو؛ ويذينغتون، بي تي (1997)، برمجة ديلان: لغة كائنية التوجه وديناميكية ، شركة أديسون ويسلي لونغمان للنشر، المحدودة.
- 1 2 كولبيكر، إي.؛ فريدمان، دي بي؛ فيليسين، إم.؛ دوبا، بي. (1986). "التوسع الكلي الصحي" (ملف PDF) . مؤتمر ACM حول لغة LISP والبرمجة الوظيفية .
- ↑ "CLHS: Function GENSYM" .
- ↑ "hygiene-versus-gensym" . community.schemewiki.org . تم الاطلاع عليه بتاريخ 11 يونيو 2022 .
- ↑ كوستانزا، باسكال؛ ديهوندت، ثيو (2010). "تضمين وحدات ماكرو متوافقة مع معايير النظافة في نظام ماكرو غير صحي" . مجلة علوم الحاسوب العالمية . 16 (2): 271-295 . CiteSeerX 10.1.1.424.5218 . doi : 10.3217/jucs-016-02-0271 .
- ↑ كولبيكر، إي.؛ واند، م. (1987). "التحليل الماكروي بالمثال: اشتقاق التحويلات النحوية من مواصفاتها" (ملف PDF) . ندوة حول مبادئ لغات البرمجة .
- ↑ باودن، أ.؛ ريس، ج. (1988). "الإغلاقات التركيبية" (ملف PDF) . لغة ليسب والبرمجة الوظيفية . مؤرشف (ملف PDF) من الأصل في 3 سبتمبر 2019.
- ↑ ديبفيج، ك؛ هيب، ر؛ بروجرمان، س (1993). "التجريد النحوي في لغة سكيم" (ملف PDF) . ليسب والحساب الرمزي . 5 (4): 295-326 . doi : 10.1007/BF01806308 . S2CID 15737919 .
- ↑ سبيربر، مايكل؛ ديبفيج، آر. كينت؛ فلات، ماثيو؛ فان ستراتن، أنطون؛ وآخرون . (أغسطس 2007). "التقرير السادس المنقح حول مخطط لغة الخوارزميات (R6RS)" . لجنة توجيه المخطط . تم الاطلاع عليه بتاريخ 13 سبتمبر 2011 .
- ↑ كلينجر، ويل (1991). "وحدات الماكرو الصحية من خلال إعادة التسمية الصريحة". ACM SIGPLAN Lisp Pointers . 4 (4): 25–28 . doi : 10.1145/1317265.1317269 . S2CID 14628409 .
- ↑ سكالسكي، ك.؛ موسكال، م.؛ أولستا، ب.، البرمجة الوصفية في نيميرل (ملف PDF) ، مؤرشف من الأصل (ملف PDF) بتاريخ 13-11-2012
- ↑ "الماكرو" .
- ↑ "البرمجة الوصفية: لغة جوليا" . مؤرشف من الأصل بتاريخ 2013-05-04 . تم الاطلاع عليه بتاريخ 2014-03-03 .
- ↑ "ملخص 6: البرامج الفرعية" . مؤرشف من الأصل بتاريخ 2014-01-06 . تم الاطلاع عليه بتاريخ 2014-06-03 .
- ↑ أولريش، سيباستيان؛ دي مورا، ليوناردو (2020-01-28). "ما وراء الرموز: توسيع الماكرو الصحي للغات إثبات النظريات". arXiv : 2001.10490 [ cs ].
- ↑ليت أوفر لامدا - 50 عامًا من لغة الليسب بقلم دوغ هويت
- ↑الخوف من وحدات الماكرو
مراجع
- في برنامج Lisp ، بول غراهام
- قواعد بناء الجملة على موقع Schemewiki
- حالة بناء الجملة على موقع Schemewiki
- أمثلة على حالة بناء الجملة على موقع SchemeWiki
- الإغلاقات النحوية على موقع Schemewiki
- simpler-macros على موقع schemewiki
- أمثلة على وحدات الماكرو المبسطة على موقع SchemeWiki
- كتابة وحدات ماكرو صحية في لغة Scheme باستخدام Syntax-Case
- لغات التحويل
- لغة البرمجة Scheme
- ديلان (لغة برمجة)
- البرمجة الوصفية
