إكسير السنانير ترجمة الوقت

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


من حيث المبدأ ، أوافق على أنه يجب عليك عدم استخدام وحدات الماكرو في عملية التعرف على اللغة. حتى الآن ، لا يمكنك أن تستيقظ في الساعة الثالثة صباحًا مع مخلفات ، أجب على السؤال عما إذا كان قد تم تنفيذ هذا الرمز في مرحلة الترجمة ، أو في وقت التشغيل. Elixir هي لغة مترجمة ، وخلال عملية التجميع ، يتم تنفيذ رمز "المستوى العلوي" ، ويتم توسيع شجرة بناء الجملة بالكامل حتى نجد أنفسنا في موقف لا يوجد فيه شيء آخر يمكن فتحه ، ويتم تجميع هذه النتيجة أخيرًا في BEAM . عندما يواجه المحول البرمجي استدعاء ماكرو في التعليمات البرمجية المصدر ، فإنه يكشف بشكل كامل AST عن ذلك وتشنجات بدلاً من المكالمة الفعلية . من المستحيل فهمه ، لا يمكن تذكره إلا.


ولكن بمجرد أن نشعر بالحرية في بناء الجملة ، سنرغب حتماً في استخدام القوة الكاملة لمجموعة الأدوات ؛ هنا دون وحدات الماكرو - في أي مكان. الشيء الرئيسي هو عدم إساءة استخدامه. يمكن لوحدات الماكرو أن تقلل بشكل كبير (إلى قيم سالبة) مقدار شفرة الترميز التي قد تكون مطلوبة ، وتوفر طريقة طبيعية ومريحة للغاية للتعامل مع AST . تستخدم Phoenix و Ecto وجميع المكتبات البارزة وحدات الماكرو بكثافة كبيرة.


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


لن أرتب صندوقًا للرمل هنا ونحت الكعك من وحدات الماكرو لأولئك الذين لم يدركوا جيدًا بعد ما يأكلونه على الإطلاق ؛ إذا كان من المثير للاهتمام البدء في التعلم من الأساسيات ، وفهم ما هو مبدئيًا ، أو كيف ولماذا يتم استخدامها في Elixir نفسها ، فمن الأفضل إغلاق هذه الصفحة فورًا وقراءة الكتاب الرائع Metaprogramming Elixir للكاتب McCris McCord ، مؤلف برنامج Phoenix Framework . أريد فقط توضيح بعض الحيل لجعل النظام الإيكولوجي الكلي الحالي أفضل.




وحدات الماكرو موثقة بشكل سيئ عن قصد. هذا السكين حاد للغاية للإعلان عن الأطفال.


هناك طريقتان لاستخدام وحدات الماكرو. أبسط ما عليك هو إرشاد المحول البرمجي بأن هذه الوحدة سوف تستخدم وحدات ماكرو من وحدة أخرى باستخدام توجيه Kernel.SpecialForms.require/2 ، وتدعو الماكرو نفسه بعد ذلك (بالنسبة لوحدات الماكرو المعرفة في نفس الوحدة النمطية ، لا يلزم وجود حاجة صريحة). في هذه المقالة ، نحن مهتمون بطريقة أخرى أكثر تعقيدًا. عند use MyLib مكالمات الرمز الخارجي use MyLib ، من المتوقع أن MyLib الوحدة النمطية MyLib بنا بتنفيذ الماكرو __using__/1 ، والذي use MyLib به المحول البرمجي عندما يصادف use MyLib . السكر النحوي ، نعم. الاتفاقية على التكوين. ماضي خوسيه لم يمر دون أثر.


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


__using__/1 يأخذ وسيطة ، لذلك يمكن لمالك المكتبة أن يسمح للمستخدمين بتمرير بعض المعلمات إليها. فيما يلي مثال من أحد المشاريع الداخلية التي تستخدم مكالمة ماكرو مع المعلمات:


 defmodule User do use MyApp.ActiveRecord, repo: MyApp.Repo, roles: ~w|supervisor client subscriber|, preload: ~w|setting companies|a 

سيتم تمرير وسيطة من نوع keyword() إلى MyApp.ActiveRecord.__using__/1 ، وهناك استخدمها لإنشاء مساعدين مختلفين للعمل مع هذا النموذج. ( ملاحظة: هذا الكود في حالة سكر لفترة طويلة لأن ActiveRecord يفقد في جميع النواحي مكالمات Ecto الأصلية).




في بعض الأحيان ، نريد قصر استخدام وحدات الماكرو على مجموعة فرعية من الوحدات (على سبيل المثال ، السماح باستخدامها فقط في الهياكل). لن يعمل التدقيق الصريح داخل تطبيق __using__/1 ، كما نود ، لأنه أثناء تجميع الوحدة النمطية لا يمكننا الوصول إلى __ENV__ (وسيكون كذلك - لم يكن مكتملًا في الوقت الذي وصل فيه المحول البرمجي مكالمة __using__/1 سيكون من المثالي إجراء هذا الفحص بعد اكتمال __using__/1 البرمجي.


لا مشكلة! هناك نوعان من سمات الوحدة التي تقوم بتكوين ذلك. مرحبا بكم في زيارتنا ، نداءات الاستعادة وقت الترجمة .


فيما يلي مقتطفات قصيرة من الوثائق.


@after_compile سيتم استدعاء رد الاتصال فورًا بعد تجميع الوحدة الحالية.

يقبل وحدة نمطية أو tuple {module, function_name} . يجب أن تأخذ رد الاتصال نفسه وسائط اثنين: بيئة الوحدة النمطية و bytecode الخاص به. عندما يتم تمرير وحدة نمطية فقط كوسيطة ، من المفترض أن هذه الوحدة النمطية تصدر الدالة __after_compile__/2 .

سوف يتم تنفيذ عمليات الاستدعاء المسجلة أولاً.
 defmodule MyModule do @after_compile __MODULE__ def __after_compile__(env, _bytecode) do IO.inspect env end end 

لا أوصي بشدة __after_compile__/2 مباشرةً في الكود الذي تم إنشاؤه ، حيث قد يؤدي ذلك إلى تعارضات مع نوايا المستخدمين النهائيين (الذين قد يرغبون في استخدام معالجاتهم الخاصة). حدد وظيفة ما في مكان ما داخل MyLib.Helpers أو أي شيء ، وقم بتمرير tuple إلى @after_compile :


 quote location: :keep do @after_compile({MyLib.Helpers, :after_mymodule_callback}) end 



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


 def struct_checker(env, _bytecode), do: env.module.__struct__ 

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




ترتبط قصة مضحكة مع @after_callback ، وهو ما يفسر تمامًا لماذا أحب OSS بشكل عام و Elixir بشكل خاص. منذ عام تقريبًا ، ارتكبت خطأً في النسخ واللصق ونسخها من مكان ما @after_callback بدلاً من @before_callback . ربما يكون الفرق بينهما واضحًا: الثاني يسمى قبل التحويل البرمجي ، ومن هناك يمكن لأي شخص تغيير شجرة بناء الجملة إلى ما بعد التعرف. وأنا - أوه ، كيف - لقد غيرتها. لكن هذا لم يؤد إلى أي نتائج في الشفرة المترجمة: لم يتغير على الإطلاق. بعد ثلاثة أكواب من القهوة ، لاحظت وجود خطأ مطبعي ، واستبدلت after before وبدأ كل شيء ؛ لكن السؤال المعذّب: لماذا أبقى المترجم صامتًا ، مثل حزبي. لقد تبين أن Module.open?/1 ترجع إلى true من رد الاتصال هذا (والذي ، من حيث المبدأ ، ليس بعيدًا عن الحقيقة - الوحدة لا تزال غير مغلقة ، والوصول إلى سماتها غير مغلق ، وتستخدم العديد من المكتبات هذا الخطأ غير الموثق).


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


لذلك كان عندما كنت بحاجة إلى إعدادات المستخدم في IO.inspect/2 ، وفي بعض الحالات. ماذا سيحدث إذا تعثرت على ذلك في جافا؟ - إنه أمر مخيف أن نتخيله.




هل لديك ماكرو لطيف!

Source: https://habr.com/ru/post/ar485810/


All Articles