مقدمة
مرحبًا جميعًا! أتمنى أن تكونوا بخير.
لقد كانت الأسابيع القليلة الماضية مليئة بالإثارة في Exercism، مع إطلاق Exercism Premium وExercism Insiders. كما أجرينا بعض المكالمات المجتمعية الرائعة، ونشرنا الكثير من التحسينات على الموقع، وهناك المزيد قادم قريبًا. هناك الكثير لنتحمّس له في الوقت الحالي، لكن لا شيء أكثر إثارة من دخولنا الشهر 6 من #12in23! صيف تعبيرات S، أو كما يُختصر بشكل أجمل إلى Summer of Sexps.
وكالعادة، ينضم إليّ حكيم عالم البرمجة، Erik.
لدينا هذا الشهر خمس لغات: Clojure وCommon Lisp وEmacs Lisp وRacket وScheme. كل من هذه اللغات لهجة من لهجات لغة Lisp؛ لذلك، بدلًا من التركيز الشديد على اللغات المختلفة في هذا الفيديو، سنلقي نظرة أوسع قليلًا على Lisp نفسها، وعلى ما يجعلها فريدة. ثم ننتهي بمراجعة موجزة لكل لغة.
لكن قبل ذلك، بعض الأمور التنظيمية! للحصول على شارة Summer of Sexps، عليك إكمال أي خمسة تمارين في إحدى هذه اللغات خلال شهر يونيو.
الشارات
هناك أيضًا شارة 12in23 التي تمتد على مدار السنة. للحصول عليها، عليك حلّ خمسة من التمارين المميّزة باللغة. إذا كنت تشاهد هذا بعد يونيو، فيمكنك إنجاز هذا الجزء في أي وقت خلال السنة، فلم يفتك شيء. ولأن كثيرين لم يعملوا مع Lisp من قبل، حاولنا اختيار تمارين بسيطة نسبيًا تمنحك فكرة عمّا تبدو عليه لغة Lisp.
- السنة الكبيسة: التعامل مع الشروط المنطقية ومفهوم صدق القيم (واختياريًا، النطاق المعجمي)
- اثنان لواحد: تنسيق سلسلة نصية والتعامل مع معامل اختياري
- فرق المربعين: استدعاء دوال يعرّفها المستخدم وإجراء عمليات حسابية بالصيغة البادئة
- اسم الروبوت: التعامل مع العشوائية والذرات والبيانات المهيكلة
- مطابقة الأقواس: استخدام الاستدعاء الذاتي للتحقق من سلسلة نصية
يمكن العثور على هذه التمارين وتمارين الأشهر السابقة جميعًا في صفحة #12in23.
نظرة عامة
إذن، اللغات المبنية على Lisp. أظن أنه ينبغي أن نبدأ بفهم القليل عن Lisp. لنبدأ بمقدمة موجزة عن Lisp عمومًا.
Lisp
-
أول ما تجدر ملاحظته أن Lisp من أقدم اللغات.
-
أنشأها John McCarthy في MIT عام 1958، في زمن كانت فيه الحواسيب لا تزال تشغل غرفًا كاملة من أعلاها إلى أسفلها 🙂
-
اسم Lisp اختصار لـ LISt Processing (أو LISt Processor)، وهو ما يبرز أهمية بنية بيانات المصفوفة.
-
صُمّمت بهدف إجراء أبحاث الذكاء الاصطناعي.
-
استندت Lisp إلى حساب لامبدا، الذي ابتكره Alonzo Church، وهو نظام صوري لوصف الحوسبة في الرياضيات (بشكل مبسّط).
-
إنها لغة بالغة التأثير، لأسباب عديدة:
-
إنها ثاني أقدم لغة برمجة عالية المستوى لا تزال مستخدمة على نطاق واسع (بعد Fortran)
-
كانت أول لغة برمجة دالية عالية المستوى، وقد قدّمت كثيرًا من الميزات التي نربطها اليوم بالبرمجة الدالية.
-
لاحظ أن Lisp كانت لا تزال تدعم البرمجة الأمرية
-
كانت أول لغة على الإطلاق تحتوي على جامع قمامة، ما حرّر المبرمج من إدارة الذاكرة يدويًا
-
قلة صياغتها النحوية نسبيًا وبساطة دلالاتها نسبيًا يجعلانها ممتازة للأغراض التعليمية.
-
لذلك تُستخدم Lisp (أو الأدق: إحدى لهجاتها) كثيرًا لتعليم البرمجة
-
وقد أنتجت (ولا تزال تنتج!) عددًا كبيرًا من لهجات اللغات (وسنتناول منها تلك المدعومة على Exercism).
-
بعبارة أخرى، في شجرة لغات البرمجة يوجد فرع منفصل للغات الشبيهة بـ Lisp (تمامًا كما يوجد فرع للغات الشبيهة بـ C).
عندما تحدثت مؤخرًا إلى Simon Peyton Jones، أحد مبتكري Haskell، كان يتحدث عن الفرق بين اللغات المبنية حول آلات تورينغ واللغات المبنية حول حساب لامبدا. لذا يستحق ذلك الحوار نظرة إذا أردت معرفة المزيد عن ذلك.
الأقواس الهلالية
توجد أقواس هلالية كثيرة في لغات Lisp، لكن هذا ليس بالضرورة أمرًا سيئًا (تمامًا كما أن كثرة الأقواس المعقوفة ليست بالضرورة سيئة في اللغات الشبيهة بـ C). تقوم لغات Lisp حول شيء يُسمى: تعبيرات S. تعبير S (اختصارًا لـ symbolic expression، ويُختصر كذلك بـ sexpr أو sexp، ومن هنا جاء اسم تحدي هذا الشهر) هو تعبير لتمثيل البيانات. وقد اختُرع وذاع صيته بفضل لغة Lisp الأصلية. يمكن أن يتخذ تعبير S أحد شكلين:
- ذرة (مثل ‘x’). فكّر فيها كـ«قيم» غير متداخلة أو أوراق الشجرة
- تعبير x . y، حيث x وy تعبيرا S. فكّر فيهما كزوجين، حيث يمكن أن يكون y العنصر التالي في المصفوفة (إن وُجد)، أو عقدتين في شجرة. لاحظ أن هذا تعريف عودي، ينتهي عند مستوى الأوراق. عادةً تُستخدم الأقواس الهلالية لهذا النوع من تعبيرات S.
تعبيرات S
تُستخدم تعبيرات S لتمثيل كلٍّ من البيانات والمصفوفات في Lisp. لذلك، كلما عرّفت مصفوفة، ستستخدم الأقواس الهلالية. وإذا جمعنا ذلك مع حقيقة أن: المصفوفة هي بنية البيانات الجوهرية في Lisp (ومن هنا اسمها)، وفي بعض لغات Lisp هي بنية البيانات الوحيدة، فستنتهي بك المطاف مع الكثير من الأقواس الهلالية. ولإظهار مدى محورية المصفوفات، فإنك في Lisp إذا أردت استدعاء دالة، تفعل ذلك بإنشاء مصفوفة.
ومن المثير للاهتمام أن العنصر الأول في المصفوفة (المعروف بالرأس head) يمثل الدالة التي تُستدعى، أما بقية العناصر (المعروفة بالذيل tail) فتُمرَّر كوسائط. تُعرف هذه بالصيغة البادئة (حيث يأتي العامل قبل القيم التي يعمل عليها)، وقد تبدو غريبة بعض الشيء في البداية، لكنها في الحقيقة مفيدة جدًا:
- يمكنك تطبيق عامل على عدة وسائط دون الحاجة إلى تكرار العامل (مثل (+ 1 2 3))
- تصبح أولوية العوامل صريحة، إذ يتعيّن عليك تعريف تعبير S جديد لاستدعاء عامل مختلف على أي حال
ومن الطريف أن المصفوفات تُستخدم حتى لتمثيل الكود المصدري، لكننا سنعود إلى ذلك لاحقًا.
عمومًا، تمتلك معظم لغات Lisp صياغة نحوية بالغة البساطة ودلالات بسيطة نسبيًا، ما يجعل تعلّمها سهلًا نسبيًا، ويجعل فهم الكود أسهل أيضًا. لكن هذه البساطة في الصياغة لا تجعلها أقل قوة! والجمع بين هذين الأمرين (صياغة بسيطة + دلالات بسيطة) يجعل لغات Lisp مثالية لكتابة المترجمات والمفسّرات. وإذا أردت يومًا بناء مترجم خاص بك، فبناء لغة Lisp خيار جيد!
ميزات رائعة في Lisp
كما ذُكر سابقًا، تستخدم لغات Lisp الأنواع والبنى نفسها داخليًا لتمثيل الكود. تُسمى هذه الخاصية homoiconicity (أو homoiconic). بعبارة أخرى، تكون اللغة homoiconic إذا أمكن التعامل مع برنامج مكتوب بها كبيانات باستخدام اللغة نفسها، وبذلك يمكن استنتاج التمثيل الداخلي للبرنامج بمجرد قراءة البرنامج ذاته. وغالبًا ما تُلخّص هذه الخاصية بالقول إن اللغة تتعامل مع الكود كبيانات.
اللغات
Scheme
- أُنشئت خلال سبعينيات القرن الماضي على يد Guy Steele وGerald Sussman في مختبر MIT AI Lab.
- بدأت كمحاولة لفهم نموذج الفاعل لدى Carl Hewitt عبر مفسّر Lisp صغير جدًا.
- قُدّمت اللغة نفسها في سلسلة من مذكرات البحث AI Memos، التي صار يُطلق عليها مجتمعةً اسم Lambda Papers.
- أول لهجة من لهجات Lisp تستخدم النطاق المعجمي (لا تكون القيم ضمن النطاق إلا حيث تُعرَّف)، ومن أولى اللغات التي تدعم الاستمرارات من الدرجة الأولى.
- لها معيار رسمي من IEEE ومعيار فعلي يُسمى Revised Report on the Algorithmic Language Scheme (RnRS).
- لها تطبيقات كثيرة: ChezScheme وGuile (وكلاهما مدعوم على Exercism) وMIT/GNU Scheme وRacket
- لغة بالغة البساطة، بقليل من الصياغة النحوية، لكن ذلك لم يكن مقصودًا.
- حاول المؤلفان بناء شيء معقّد، لكنهما انتهيا إلى تصميم شيء أبسط بكثير مما قصداه
- الاستدعاء الذاتي الطرفي السليم. والطريقة المألوفة لإجراء التكرار هي عبر الاستدعاء الذاتي.
- تُحسّن Scheme نداءات الاستدعاء الذاتي الطرفية كي لا تستهلك مساحة المكدّس أو موارد أخرى. وهذا يعني أنه يمكن استخدام الاستدعاء الذاتي على بيانات كبيرة بشكل اعتباطي أو لحسابات طويلة بشكل اعتباطي
- أنواع بيانات عددية قوية، بما في ذلك الأعداد النسبية والمركّبة
- التقييم المؤجَّل، وهو شبيه بالوعود.
- نظام ماكرو قوي.
- تُقلّل الماكرو النظيفة احتمال ظهور نتائج غير متوقعة عند تعريف الماكرو.
Common Lisp
- بدأ العمل على Common Lisp عام 1981 بعد مبادرة من مدير ARPA، Bob Engelmore، لتطوير لهجة Lisp واحدة معيارية للمجتمع، لأن اللهجات المختلفة المستخدمة كانت غالبًا غير متوافقة، وهذا يعني أن الكود والمعرفة لم يكونا قابلين للمشاركة
- نُشر المعيار الأول عام 1984 والأخير عام 1994 (مواصفة مستقرة جدًا)
- ولكونه معيارًا، توجد تطبيقات مختلفة له، مثل Steel Bank Common Lisp (وهو الافتراضي على Exercism) وCLisp.
- توجد أيضًا تطبيقات تجارية، مثل Allegro CL وLispWorks، وكذلك ECL (Embeddable Common Lisp) الذي يمكن تضمينه في برامج C، وABCL الذي يعمل على Java Virtual Machine.
- معرَّف بمعيار (ANSI INCITS 226-1994)، لذا لا يزال كود كُتب قبل 30 عامًا يعمل جيدًا اليوم
- نظام أنواع غني وقابل للتوسيع
- مصمّمة للتطوير عبر الصور وREPL، لذا فهي شديدة القابلية للتفحّص الذاتي.
Emacs Lisp
- طُوّرت عام 1985 بهدف الحصول على لغة فعّالة لتوسيع محرّر نصوص
- ذات أنواع ديناميكية
- نحو 80% من Emacs مكتوبة بـ Emacs Lisp (و20% بـ C لأسباب تتعلق بالأداء)
- تختلف قليلًا عن لغات Lisp الأخرى:
- غير معيارية، ولا تزال تتطور ببطء
- لا يوجد حذف تلقائي للنداءات الطرفية، ويدعم ذلك عبر ماكرو named-let (الذي يحوّلها إلى حلقة while)
- النطاق الديناميكي افتراضيًا، ويُنصح بالنطاق المعجمي للكود الجديد
- توثيق جيد داخل المحرّر
- متعددة المنصات (تعمل في أي مكان يعمل فيه Emacs)
- تعلّم اللغة بقراءة الكود الخاص بالوظائف التي تستخدمها يوميًا (نواة Emacs + الحزم)
- تتوفر مجموعة فرعية من Common Lisp عبر حزمة cl-lib. وبينما تميل Emacs Lisp إلى البساطة الشديدة، فإن Common Lisp أغنى بكثير بالميزات. وتتيح حزمة cl-lib مجموعة فرعية من CL
Racket
- أسّس Matthias Felleisen شركة PLT Inc.، التي قرّرت في يناير 1995 تطوير بيئة برمجة تعليمية قائمة على Scheme. كانت تُسمى في الأصل PLT Scheme، ثم غُيّر اسمها لاحقًا إلى Racket.
- إلى جانب كونها بيئة برمجة تعليمية، صُمّمت كمنصة لتصميم لغات البرمجة وتنفيذها.
- لغة LISP حديثة، سليلة Scheme
- تدعم البرمجة المنطقية!
- صياغة نحوية بسيطة ومعبّرة، مثالية للمبتدئين وقوية في يد الخبراء
- تدعم أنماط برمجة كثيرة: البرمجة الدالية، والبرمجة كائنية التوجه، والتصميم بالعقد، والبرمجة المنطقية، والبرمجة الفوقية
- مكتبة معيارية شاملة
- تأتي مع DrRacket، وهي بيئة تطوير متكاملة كاملة مصمّمة للتعلّم والاستكشاف بأقل قدر من العناء
- توثيق ممتاز مليء بالمعلومات السياقية والأمثلة
Clojure
- طوّرها Rich Hickey بهدف الحصول على LISP حديثة تعمل على JVM وبقدرة ممتازة على التوازي
- لهجة من LISP، لكنها تختلف بعض الشيء عن لغات LISP الأخرى في أنها لا تدعم الاستدعاء الذاتي الطرفي الضمني (لا تقلق إن لم تكن تعرف ما هذا)، ولديها بنى بيانات أكثر من مجرد المصفوفات: الخرائط والمجموعات والمتجهات. ولكل من هذه البنى صياغتها الحرفية الخاصة.
- وهي جميعًا غير قابلة للتغيير، ومع ذلك تتمتع بأداء ممتاز مع بحث بترتيب O(log32 n) وهو زمن ثابت «فعليًا»
- تعدد أشكال وقت التشغيل عبر الدوال المتعددة والبروتوكولات
- تفاعل ممتاز مع JVM
- نظام Clojure Spec لتوصيف البيانات (وقت التشغيل، لا وقت الترجمة)، يتيح لك تعريف بنية البيانات وتوليد البيانات والاختبار القائم على الخصائص والمزيد
حالات الاستخدام
Scheme
- تُستخدم في التعليم للمساعدة في تدريس علوم الحاسوب (الكتاب المؤثّر Structure and Interpretation of Computer Programs يستخدم Scheme).
- تُستخدم في الذكاء الاصطناعي. وتُستخدم كلغة برمجة نصية، مثلًا في GIMP (محرّر الرسوميات)، وأدوات CAD (التصميم بمساعدة الحاسوب)، وحتى في الأفلام، عبر نصوص إدارة محرّك العرض في فيلم Final Fantasy: The Spirit Within
Common Lisp
- تُستخدم Common Lisp في أماكن كثيرة، مثل الذكاء الاصطناعي والبحث، وكذلك في التطبيقات التجارية: فقد كتبت NASA برنامج الطيار الآلي لمركبة الفضاء Deep Space One بلغة Common Lisp، وكُتب Viaweb بلغة Common Lisp، وقد استحوذت عليه Yahoo لاحقًا وأعادت تسميته Yahoo Store!، وكذلك الإصدار الأول من Reddit
Emacs Lisp
- تُستخدم Emacs Lisp في... حسنًا، في Emacs!
- في جوهره، Emacs هو مفسّر لـ Emacs Lisp، وهي لهجة من لغة البرمجة Lisp لكن مع إضافات لدعم تحرير النصوص
Racket
- تُستخدم في التعليم، إذ صُمّمت Racket مع التركيز على دعم إنشاء اللغات وتبسيطها وتحليلها.
- تُستخدم في البحث، لأن صياغتها ودلالاتها القابلة للتوسيع تجعلها مناسبة لتصميم لغات وميزات لغوية جديدة وبناء نماذجها الأولية.
- تُستخدم في الألعاب، مثلًا من John Carmack (صاحب الشهرة في Doom) في بيئة برمجة نصية تفاعلية للواقع الافتراضي، واستخدمها المطوّر Naughty Dog في البرمجة النصية (مثلًا في Uncharted). وHacker News مكتوبة بـ Arc، وهي أيضًا لغة Lisp، وهي نفسها مكتوبة بـ Racket.
Clojure
- تُستخدم Clojure في أمور كثيرة ومختلفة، من بينها استحواذ Docker على Atomist في 2022، وهي منصة لأمن الحاويات والأتمتة منفّذة بـ Clojure.
- أكبر مستخدم لـ Clojure في العالم هو Nubank، وهو بنك ناشئ، استحوذ عليها قبل بضع سنوات ويوظّف الآن فريق Clojure الأساسي.
- تُستخدم بكثافة في بناء النماذج الأولية السريعة، لأنها ديناميكية وتفاعلية للغاية.
منظور البرمجة
تدعم جميع اللغات الأنماط الدالية والأمرية والرمزية. وبعضها يدعم أيضًا البرمجة كائنية التوجه، وأبرزها Common Lisp.
معظم لغات Lisp هي لغات ديناميكية، رغم أن Racket تدعم الأنواع الثابتة.
وهذا لا يعني أنها كلها مُفسَّرة، إذ هناك مزيج من: مُفسَّرة (دون أي خطوة ترجمة)، ومترجمة إلى كود بايتي ثم مُفسَّرة، ومترجمة مباشرة إلى كود الآلة.
Scheme
- بالغة البساطة، بدلالات واضحة وبسيطة وطرق قليلة لتكوين التعابير.
- تجعل تعلّم اللغة وفهم الكود أمرًا سهلًا.
- ولهذا السبب، تُستخدم Scheme كثيرًا أيضًا في كثير من مقررات علوم الحاسوب التمهيدية
- استمرارات من الدرجة الأولى.
- الاستمرار تمثيل لحالة البرنامج.
- يمكن استخدام الاستمرارات لنمذجة مسار التحكم (مثل بنية
return) أو الروتينات المتزامنة (التي تتيح تعدد المهام)
Common Lisp
- نظام كائني التوجه قابل للتوسيع مع توليفات طرق قابلة للبرمجة (سواء في كيفية دمج طرق الأصناف الفرعية/الفائقة، وكذلك طرق before وafter وaround للسماح بتوسيع الأنظمة دون تعديلها)
- نظام شروط قابل للبرمجة (وهو مجموعة شاملة تضم «الاستثناءات») يتيح فصل التعرّف على الشروط عن اختيار كيفية التعامل معها. ونظام الشروط أكثر مرونة من أنظمة الاستثناءات، لأنه بدلًا من تقديم تقسيم من شقّين بين الكود الذي يشير إلى خطأ1 والكود الذي يعالجه2، يقسّم نظام الشروط المسؤوليات إلى ثلاثة أجزاء: الإشارة إلى شرط، ومعالجته، وإعادة التشغيل.
- تتيح الماكرو توسيع صياغة اللغة النحوية، لا مجرد توليد كود نمطي متكرر. ويساعد ذلك على بناء لغة تناسب المجال بدلًا من العكس.
Emacs Lisp
- دعم وتكامل ممتازان مع المحرّر
- يمكن استخدامها لتخصيص Emacs وهو يعمل («مثل إجراء جراحة دماغية على نفسك» :))
- يمكن استخدامها في الوضع الدفعي، حيث تتاح لك كل قدرات المحرّر على معالجة النصوص (مثل المخازن المؤقتة وأوامر التنقّل)
Racket
- نظام ماكرو قوي. وتبنى عليه السكّر النحوي مثل ماكرو threading. والماكرو أيضًا نظيفة، وهي الإجابة عن سؤال بسيط: يولّد الماكرو كودًا يُودَع في مكان آخر. وعند تقييم ذلك الكود، كيف نحدّد ارتباطات المعرّفات داخله؟ تُقلّل الماكرو النظيفة احتمال ظهور نتائج غير متوقعة عند تعريف الماكرو.
- موجّهة نحو اللغات.
- تأتي Racket مزوّدة بالأدوات اللازمة لكتابة لغة برمجة خاصة بك أو DSL، مبنية على ماكرو Racket.
- عدة لغات مضمّنة، مثل Racket ذات الأنواع (تدعم تعليقات الأنواع التي يُتحقَّق منها بشكل ثابت)، وdatalog (لغة شبيهة بـ Prolog)، والتي تدعمها بيئة DrRacket، وscribble، وهي أداة لإنشاء مستندات نصية بصيغة HTML أو PDF
- REPL جزء جوهري من سير عمل التطوير، وليس فقط لتجربة الأمور والبحث في التوثيق
Clojure
- نظام ماكرو قوي.
- ويُبنى عليه السكّر النحوي مثل ماكرو threading
- REPL جزء جوهري من سير عمل التطوير، وليس فقط لتجربة الأمور والبحث في التوثيق
أيّها تجرّب
- إذا لم تجرّب لغة Lisp من قبل، فإن Scheme وRacket خياران رائعان لأن كليهما ذو صياغة نحوية بسيطة جدًا.
- ومع ذلك، فإن Common Lisp وClojure لديهما وضع التعلّم، لذا هما على الأرجح الأفضل للتعلّم على Exercism.
- إذا كنت تستخدم Emacs بالفعل، فإن Emacs Lisp خيار طبيعي.
- وكذلك، إذا كنت تستخدم لغة تعمل على JVM، فإن Clojure خيار طبيعي.
- تتمتع Emacs Lisp (عبر Emacs) وClojure (عبر IntelliJ) وRacket (عبر DrRacket) كلها بدعم ممتاز في بيئات التطوير المتكاملة IDE.
- وبالطبع توجد بيئات تطوير متكاملة جيدة أيضًا لـ Common Lisp وScheme.
- إذا أردت لغة Lisp غنية بالميزات حقًا، فإن Common Lisp وClojure وRacket واسعة النطاق فعلًا
- وإذا أردت لغة Lisp مختلفة بعض الشيء، فإن Clojure تمتلك صياغة نحوية فريدة إلى حد ما بالنسبة لـ Lisp.
- إذا كنت مهتمًا بالماكرو والبرمجة الفوقية، فهي جميعًا خيارات جيدة أساسًا! لكن إذا أردت بناء لغات جديدة، فإن Racket على وجه الخصوص رائعة
بالطبع، إذا كان لديك الوقت، أنصحك بتجربة لغة أو لغتين! ولا تخف من الأقواس الهلالية! أعلم أنني كنت خائفًا منها، ولهذا أخّرت تعلّم Lisp مدة طويلة. لكنك ستعتاد عليها سريعًا، وربما تصل إلى تقديرها، كما حدث معي. في الواقع، أنا الآن أحب لغات Lisp، بصياغتها البسيطة ودلالاتها السهلة مع بقائها معبّرة للغاية.