برمجة أفضل بلغة OCaml
1. برمجة أفضل بلغة OCaml
هل تعرف بالفعل كيف تبرمج بلغة سائدة مثل Python أو Java؟ جيد. هذا الكتاب لك. لقد حان وقت تعلم البرمجة على نحو أفضل. وحان وقت تعلم لغة وظيفية (functional language)، هي OCaml.
تقدم البرمجة الوظيفية منظورًا مختلفًا للبرمجة عمّا عشته حتى الآن. ويتطلب التكيّف مع ذلك المنظور التخلي عن أفكار قديمة: عبارات الإسناد (assignment statements)، والحلقات، والأصناف والكائنات، وغيرها. ولن يكون ذلك سهلًا.
استقبل نان-إن (Nan-in)، أستاذ ياباني في عصر ميجي (1868-1912)، أستاذًا جامعيًا جاء ليسأل عن الزِن. وقدّم له نان-إن الشاي. فملأ كوب زائره حتى حافته، ثم واصل الصب. وظل الأستاذ يراقب الفائض حتى لم يعد يستطيع كبح نفسه. فقال: «لقد امتلأ الكوب. لن يدخله المزيد!» فقال نان-إن: «كهذا الكوب، أنت ممتلئ بآرائك وتخميناتك. فكيف أُريك الزِن إن لم تفرغ كوبك أولًا؟»
أعتقد أن تعلّم OCaml سيجعلك مبرمجًا أفضل. وإليك السبب:
- ستختبر حرية عدم القابلية للتغيير (immutability)، حيث لا يمكن لقيم ما يُسمى «المتغيرات» أن تتغير. وداعًا للتنقيح.
- ستتحسن في التجريد (abstraction)، وهو ممارسة تجنّب التكرار عبر استخلاص القواسم المشتركة. وداعًا للشيفرة المتضخمة.
- ستتعرض لـ نظام الأنواع (type system) ستكرهه في البداية لأنه يرفض برامج تعتقد أنها صحيحة. لكنك ستصل إلى حبه، لأنك ستدرك بتواضع أنه كان على حق وأن برامجك كانت خاطئة. وداعًا للاختبارات الفاشلة.
- ستتعرض لجزء من نظرية لغات البرمجة وتنفيذها، مما يساعدك على فهم الأسس لما تقوله للحاسوب عند كتابة الشيفرة. وداعًا للتعاويذ الغامضة والسحرية.
كل هذه الأفكار يمكن تعلّمها في سياقات ولغات أخرى. لكن OCaml يقدّم فرصة مذهلة لجمعها كلها معًا. ستغيّر OCaml الطريقة التي تفكر بها في البرمجة.
«اللغة التي لا تؤثر في طريقة تفكيرك في البرمجة ليست جديرة بأن تُعرف.» — ألان ج. بيرليس (1922-1990)، أول حاصل على جائزة تورينغ
علاوة على ذلك، OCaml جميلة. إنها أنيقة وبسيطة ورائعة. والجماليات مهمة بالفعل. فالشيفرة لا تُكتب لتُنفَّذ بواسطة الآلات فحسب، بل تُكتب أيضًا للتواصل مع البشر. والشيفرة الأنيقة أسهل في القراءة والصيانة، وإن لم تكن بالضرورة أسهل في الكتابة.
يمكن للشيفرة التي تكتبها بلغة OCaml أن تكون أنيقة وذوّاقة. وقد لا يبدو ذلك واضحًا في البداية. فأنت تتعلم لغة جديدة بعد كل شيء — ولن تتوقع أن تستمتع بشعر السنسكريتية في اليوم الأول من مقرر تمهيدي في السنسكريتية. بل من المرجح أن تشعر بالإحباط لفترة وأنت تكافح للتعبير عن نفسك بلغة جديدة. لذا امنح الأمر بعض الوقت. وبعد أن تتقن OCaml، قد تفاجأ بمدى قبح اللغات الأخرى التي تعرفها بالفعل عندما تعود إليها.
1.1. ماضي OCaml
من حيث النسب، تنحدر OCaml من سلالة لغات البرمجة التي يُعدّ Lisp جدّها الأكبر، وتضم لغات حديثة أخرى مثل Clojure وF# وHaskell وRacket.
نشأت OCaml من العمل الذي قام به روبن ميلنر وآخرون في مختبر إدنبرة لعلوم الحاسوب في اسكتلندا. كانوا يعملون على مُبرهنات النظريات (theorem provers) في أواخر السبعينيات وأوائل الثمانينيات. وتقليديًا، كانت مُبرهنات النظريات تُنفَّذ بلغات مثل Lisp. وظل ميلنر يصطدم بمشكلة أن مُبرهنات النظريات كانت تجمع أحيانًا «براهين» غير صحيحة (أي ليست براهين) وتزعم أنها صحيحة. فحاول تطوير لغة لا تسمح لك إلا ببناء براهين صحيحة. وكانت النتيجة ML، التي ترمز إلى «Meta Language» (اللغة الفوقية). وقد بُني نظام الأنواع في ML بعناية بحيث لا تستطيع بناء براهين صحيحة إلا في تلك اللغة. ثم كُتب مُبرهن النظريات بوصفه برنامجًا يبني برهانًا. وفي النهاية، تطوّرت هذه «ML الكلاسيكية» إلى لغة برمجة كاملة.
في أوائل الثمانينيات، وقع انقسام في مجتمع ML، إذ وقف الفرنسيون في جانب والبريطانيون والأمريكيون في جانب آخر. فمضى الفرنسيون لتطوير CAML ثم Objective CAML (أي OCaml)، بينما طوّر البريطانيون والأمريكيون Standard ML. واللهجتان متشابهتان تمامًا. وطرح Microsoft نسختها الخاصة من OCaml باسم F# في عام 2005.
حصل ميلنر على جائزة تورينغ في عام 1991، ويعود ذلك في جزء كبير منه إلى عمله على ML. ويتضمن موقع ACM الخاص بجائزته هذا الثناء:
كانت ML متقدمة كثيرًا على عصرها. فهي مبنية على أفكار رياضية نقية ومصاغة بعناية، فُصلت عن بعضها ليُدرس كل منها على حدة وليسهل دمجها وإعادة استخدامها. وقد أثّرت ML في لغات عملية كثيرة، منها Java وScala وF# من Microsoft. وبالفعل، لا ينبغي لأي مصمم لغات جاد أن يتجاهل هذا النموذج من التصميم الجيد.
1.2. حاضر OCaml
OCaml هي لغة برمجة وظيفية. والتجريد اللغوي الأساسي في اللغات الوظيفية هو الدالة الرياضية. فالدالة تربط مدخلًا بمخرج؛ وللمدخل نفسه، تنتج دائمًا المخرج نفسه. أي أن الدوال الرياضية بلا حالة (stateless): فهي لا تحتفظ بأي معلومات إضافية أو حالة (state) تبقى بين استخدامات الدالة. والدوال من الرتبة الأولى (first-class): يمكنك استخدامها كمدخلات لدوال أخرى، وإنتاج دوال كمخرجات. والتعبير عن كل شيء بواسطة الدوال يتيح نموذجًا برمجيًا موحّدًا وبسيطًا يسهل الاستدلال عليه مقارنةً بالإجراءات والطرق الموجودة في عائلات اللغات الأخرى.
تتضمن لغات البرمجة الأمرية (imperative) مثل C وJava حالة قابلة للتغيير (mutable) تتغيّر طوال التنفيذ. وتحدد الأوامر (commands) كيفية الحساب عبر تغيير تلك الحالة تغييرًا هدّامًا. ويمكن أن تكون للإجراءات (أو الطرق) آثار جانبية (side effects) تحدّث الحالة إضافةً إلى إنتاج قيمة مرجعة.
وهم القابلية للتغيير هو أنها سهلة الاستدلال عليها: فالآلة تفعل هذا، ثم هذا، وهكذا.
حقيقة القابلية للتغيير هي أنه بينما تبرع الآلات في التلاعب المعقّد بالحالة، لا يبرع البشر في فهمه. وجوهر سبب ذلك أن القابلية للتغيير تكسر الشفافية المرجعية (referential transparency): أي القدرة على استبدال تعبير بقيمته دون التأثير في نتيجة الحساب. ففي الرياضيات، إذا كان ، فيمكنك استبدال أينما رأيت . أما في اللغات الأمرية فلا يمكنك ذلك: فقد تكون لـ آثار جانبية، لذا قد يؤدي حساب في الزمن إلى قيمة مختلفة عن حسابها في الزمن .
من المغري أن نعتقد أن هناك حالة واحدة تتلاعب بها الآلة، وأن الآلة تفعل شيئًا واحدًا في كل مرة. وتبذل أنظمة الحاسوب جهدًا كبيرًا في محاولة تقديم ذلك الوهم. لكنه مجرد وهم لا غير. ففي الواقع، هناك حالات كثيرة موزّعة على الخيوط (threads) والأنوية (cores) والمعالجات (processors) والحواسيب المتصلة بالشبكة. وتفعل الآلة أشياء كثيرة في الوقت نفسه. وتجعل القابلية للتغيير الاستدلال على الحالة الموزعة والتنفيذ المتزامن أمرًا بالغ الصعوبة.
لكن عدم القابلية للتغيير يحرّر المبرمج من هذه الهواجس. فهو يوفر طرقًا قوية لبناء برامج صحيحة ومتزامنة. وOCaml لغة غير قابلة للتغيير في الأساس، شأنها شأن معظم اللغات الوظيفية. وهي تدعم البرمجة الأمرية بحالة قابلة للتغيير، لكننا لن نستخدم تلك الميزات إلا بعد عدة فصول من الكتاب — ويعود ذلك جزئيًا إلى أننا ببساطة لن نحتاج إليها، وجزئيًا إلى أن نجعلك تقلع «إقلاعًا مفاجئًا» عن تبعية قد لا تعرف أنك مصاب بها. وحرية التحرر من القابلية للتغيير هذه من أكبر تغييرات المنظور التي يمكن أن يمنحك إياها OCaml.
1.2.1. ميزات OCaml
OCaml لغة برمجة ثابتة الأنواع (statically-typed) وآمنة الأنواع (type-safe). واللغة ثابتة الأنواع تكتشف أخطاء الأنواع في وقت التصريف؛ فإذا اكتُشف خطأ نوعي، لا تسمح اللغة بتنفيذ البرنامج. واللغة آمنة الأنواع تحدّ من أنواع العمليات التي يمكن إجراؤها على أنواع البيانات. وعمليًا، يمنع ذلك كثيرًا من الأخطاء السخيفة (مثل معاملة عدد صحيح كدالة)، ويمنع أيضًا كثيرًا من المشكلات الأمنية: فقد كان أكثر من نصف حالات الاختراق المُبلَّغ عنها لدى فريق الاستجابة لحالات الطوارئ الحاسوبية (CERT، وهي وكالة حكومية أمريكية مكلفة بالأمن السيبراني) ناتجًا عن طفح المخازن (buffer overflows)، وهو أمر مستحيل في لغة آمنة الأنواع.
بعض اللغات، مثل Python وRacket، آمنة الأنواع لكنها ديناميكية الأنواع (dynamically typed). أي أن أخطاء الأنواع لا تُكتشف إلا في وقت التشغيل. ولغات أخرى، مثل C وC++، ثابتة الأنواع لكنها غير آمنة الأنواع: فهي تتحقق من بعض أخطاء الأنواع، لكنها لا تضمن غياب جميعها. أي أنه لا يوجد ضمان بعدم حدوث خطأ نوعي في وقت التشغيل. ولا تزال لغات أخرى، مثل Java، تستخدم مزيجًا من الكتابة الثابتة والديناميكية لتحقيق أمان الأنواع.
تدعم OCaml عددًا من الميزات المتقدمة، بعضها ربما صادفته من قبل، وبعضها من المرجح أن يكون جديدًا:
- أنواع البيانات الجبرية (algebraic data types): يمكنك بناء بنى بيانات متطورة في OCaml بسهولة، دون عناء المؤشرات وإدارة الذاكرة. كما أن التطابق النمطي (pattern matching) — وهي ميزة سنتعلمها قريبًا وتتيح فحص شكل بنية البيانات — يجعلها أكثر ملاءمة.
- استدلال الأنواع (type inference): لست مضطرًا إلى كتابة معلومات الأنواع في كل مكان. فالمصرّف يستنتج معظم الأنواع تلقائيًا. وهذا يمكن أن يسهّل قراءة الشيفرة وصيانتها.
- تعدد الأشكال الوسيطي (parametric polymorphism): يمكن تعميم الدوال وبنى البيانات على الأنواع. وهذا أمر جوهري لتمكين إعادة استخدام الشيفرة.
- جمع القمامة (garbage collection): تعفيك الإدارة التلقائية للذاكرة من عبء تخصيص الذاكرة وتحريرها، وهو مصدر شائع للأخطاء في لغات مثل C.
- الوحدات (modules): يسهّل OCaml هيكلة الأنظمة الكبيرة عبر استخدام الوحدات. وتُستخدم الوحدات لتغليف التنفيذات خلف واجهات (interfaces). ويتجاوز OCaml بكثير وظائف معظم اللغات في الوحدات، إذ يوفر دوال (تسمى مُوظِّفات (functors)) تتعامل مع الوحدات.
1.2.2. OCaml في الصناعة
ليست OCaml وغيرها من اللغات الوظيفية بقريبة من شعبية Python أو C أو Java. وتكمن قوة OCaml الحقيقية في التعامل مع اللغات (أي المصرّفات والمحللات (analyzers) والمدققات (verifiers) والمُبرهنات وغيرها). ولا عجب في ذلك، لأن OCaml تطورت من مجال إثبات النظريات.
ولا يعني ذلك أن اللغات الوظيفية لا تُستخدم في الصناعة. فهناك العديد من المشاريع الصناعية التي تستخدم OCaml والتي تستخدم Haskell، بين لغات أخرى. بل إن يارون مينسكي (دكتوراه من كورنيل عام 2002) كتب ورقة عن استخدام OCaml في الصناعة المالية. وهي تشرح كيف تجعل ميزات OCaml منها خيارًا جيدًا لبناء برمجيات معقدة تعمل بسرعة.
1.3. انظر إلى مستقبلك
تأتي اللغات عامة الغرض وتذهب. وطوال حياتك، من المرجح أن تتعلم عددًا منها. فاليوم هي Python وJava. وأمس كانت Pascal وCobol. وقبل ذلك كانت Fortran وLisp. ومن يدري ماذا ستكون غدًا؟ في هذا المجال سريع التغير، تحتاج إلى القدرة على التكيّف بسرعة. فعلى المبرمج الجيد أن يتعلم المبادئ الكامنة وراء البرمجة التي تتجاوز تفاصيل أي لغة بعينها. ولا سبيل أفضل لبلوغ هذه المبادئ من مقاربة البرمجة من منظور وظيفي. وتعلّم لغة جديدة من الصفر يتيح الفرصة للتأمل على الطريق في الفرق بين البرمجة والبرمجة بلغة ما.
وإذا أردت بعد OCaml أن تتعلم المزيد عن البرمجة الوظيفية، فستكون مستعدًا جيدًا. فOCaml تقوم بعمل رائع في توضيح جوهر البرمجة الوظيفية وتبسيطه، على نحو لا تفعله لغات أخرى تمزج بين البرمجة الوظيفية والأمرية (مثل Scala) أو تدفع البرمجة الوظيفية إلى أقصى حدودها (مثل Haskell).
وحتى إن لم تبرمج بلغة OCaml مرة أخرى بعد تعلمها، فستظل أكثر استعدادًا للمستقبل. فالميزات المتقدمة في اللغات الوظيفية تنزع بشكل مفاجئ إلى التنبؤ بميزات جديدة في اللغات الأكثر شيوعًا. فقد أدخلت Java جمع القمامة إلى التيار السائد في عام 1995؛ وكانت Lisp تمتلكه في عام 1958. ولم تحصل Java على الأنواع العامة (generics) إلا في الإصدار 5 عام 2004؛ بينما كانت عائلة ML تمتلكها في عام 1990. وقد أُدرجت الدوال من الرتبة الأولى واستدلال الأنواع والتطابق النمطي في لغات سائدة مثل Python وJava وC++، بعد وقت طويل من طرح اللغات الوظيفية لها.
1.4. لمحة تاريخية عن CS 3110
هذا الكتاب هو المقرر الأساسي لمادة CS 3110 في جامعة كورنيل. وقد وُجد المقرر منذ أكثر من عقدين، ودرّس البرمجة الوظيفية دائمًا، لكنه لم يستخدم OCaml دائمًا.
في قديم الزمان، كان هناك مقرر في MIT يُعرف باسم 6.001 Structure and Interpretation of Computer Programs (SICP). وكان له كتاب بالاسم نفسه، واستخدم لغة Scheme، وهي لغة برمجة وظيفية. وقد درّس تيم تيتلبوم نسخة من المقرر في كورنيل في خريف 1988، متبعًا الكتاب عن قرب، مستخدمًا لغة Scheme.
CS 212. كان دان هوتنلوخر مساعد تدريس لمقرر 6.001 في MIT، ثم أصبح لاحقًا عضوًا في هيئة التدريس في كورنيل. وفي خريف 1989، أنشأ مقرر CS 212 Modes of Algorithm Expression. وبناءً على SICP، أضفى على المادة مقاربة أكثر صرامة. وواصل هوتنلوخر تطوير CS 212 حتى منتصف التسعينيات، باستخدام لهجات محلية متنوعة من Scheme.
وبدأ أعضاء آخرون في هيئة التدريس بتدريس المقرر بانتظام. كان رامين زابيح قد درس 6.001 كطالب في سنته الأولى في MIT. وفي ربيع 1994، وقد أصبح عضوًا في هيئة التدريس في كورنيل، درّس CS 212. ودرّس دكستر كوزن (دكتوراه من كورنيل عام 1977) المقرر لأول مرة في ربيع 1996. ويبدو أن أقدم سجل إلكتروني باقٍ للمقرر هو ربيع 1998، وقد درّسه غريغ موريسيت بلغة Dylan؛ وكان اسم المقرر قد أصبح Structure and Interpretation of Computer Programs.
وبحلول خريف 1999، كان لـ CS 212 ملاحظات محاضرات خاصة به. وكما هو الحال في CS 3110 حتى الآن، غطّت تلك النسخة من CS 212 البرمجة الوظيفية، ونموذجي الإحلال والبيئة، وبعض بنى البيانات والخوارزميات، وتنفيذ لغات البرمجة.
CS 312. في ذلك الوقت، كان في منهج علوم الحاسوب مقرران تمهيديان للبرمجة: CS 211 Computers and Programming وCS 212. وكان الطلاب يدرسون أحدهما أو الآخر، على غرار ما يفعله الطلاب اليوم إذ يدرسون إما CS 2110 أو CS 2112. ثم درسوا CS 410 Data Structures. ويبدو أن أقدم سجل إلكتروني باقٍ لـ CS 410 يعود إلى ربيع 1998. وقد غطّى كثيرًا من بنى البيانات والخوارزميات التي لم يغطها CS 212، بما فيها الأشجار المتوازنة والرسوم البيانية (graphs)، واستخدم لغة Java.
واعتمادًا على المقرر الذي درسوه، CS 211 أو 212، كان الطلاب يدخلون المقررات المتقدمة بمجموعات مهارات مختلفة. وبعد مناقشات مستفيضة، اختارت هيئة التدريس جعل CS 211 إلزاميًا، وإعادة تسمية CS 212 ليصبح CS 312 Data Structures and Functional Programming، وجعل CS 211 متطلبًا سابقًا لـ CS 312. وفي الوقت نفسه، حُذف CS 410 من المنهج ووُزّع محتواه على CS 312 وCS 482 Introduction to Analysis of Algorithms. ودرّس دكستر كوزن آخر طرح لـ CS 410 في خريف 1999.
وافتتح غريغ موريسيت مقرر CS 312 الجديد في ربيع 2001. وانتقل من Scheme إلى Standard ML. ودرّسه كوزن لأول مرة في خريف 2001، وأندرو مايرز في خريف 2002. وبدأ مايرز بإدراج مادة عن البرمجة المعيارية من كتاب مدرسي آخر من MIT، هو Program Development in Java: Abstraction, Specification, and Object-Oriented Design لباربرا ليسكوف وجون غوتاغ. ودرّس هوتنلوخر المقرر لأول مرة في ربيع 2006.
CS 3110. في خريف 2008 حدث تغييران كبيران: تحوّلت اللغة إلى OCaml، وتحوّلت الجامعة إلى أرقام مقررات من أربعة أرقام. فأصبح CS 312 هو CS 3110. ودرّس مايرز وهوتنلوخر وكوزن وزابيح المقرر المنقّح لأول مرة في خريف 2008 وربيع 2009 وخريف 2009 وخريف 2010 على الترتيب. ودرّس نيت فوستر المقرر لأول مرة في ربيع 2012؛ وشارك بوب كونستابل ومايكل جورج في تدريسه للمرة الأولى في خريف 2013.
ودرّس مايكل كلاركسون (دكتوراه من كورنيل عام 2010) المقرر لأول مرة في خريف 2014، بعد أن عمل مساعد تدريس للمقرر كطالب دكتوراه في ربيع 2008. وبدأ بتنقيح عرض مادة برمجة OCaml ليدمج أفكار دان غروسمان (دكتوراه من كورنيل عام 2003) حول مقاربة مبدئية لتعلّم لغة برمجة عبر تفكيكها إلى الصياغة والدلالات الديناميكية والدلالات الساكنة. ويستخدم غروسمان تلك المقاربة في مقرر CSE 341 Programming Languages في جامعة واشنطن وفي مقرره المفتوح عبر الإنترنت الشهير عن لغات البرمجة.
وفي خريف 2018 بدأ تجميع هذا الكتاب. وهو يجمع عمل أكثر من عقدين من تدريس البرمجة الوظيفية في كورنيل. وعلى حد تعبير أغنية كورنيل المسائية،
إنه صدى من الجدران، من جامعتنا، كورنيل الجميلة.
1.5. الملخّص
هذا الكتاب يدور حول أن تصبح مبرمجًا أفضل. وستساعدك دراسة البرمجة الوظيفية في ذلك. وأكبر عقبة في طريقنا هي الإحباط الناتج عن التحدث بلغة جديدة، ولا سيما التخلي عن الحالة القابلة للتغيير. لكن الفوائد ستكون عظيمة: اكتشاف أن البرمجة تتجاوز البرمجة بأي لغة أو عائلة لغات بعينها، والتعرض لميزات لغوية متقدمة، وتقدير الجمال.
1.5.1. المصطلحات والمفاهيم
- الكتابة الديناميكية (dynamic typing)
- دوال من الرتبة الأولى (first-class functions)
- لغات البرمجة الوظيفية (functional programming languages)
- عدم القابلية للتغيير (immutability)
- Lisp
- ML
- OCaml
- الشفافية المرجعية (referential transparency)
- الآثار الجانبية (side effects)
- الحالة (state)
- الكتابة الثابتة (static typing)
- أمان الأنواع (type safety)
1.5.2. قراءات إضافية
- Introduction to Objective Caml، الفصلان 1 و2، كتاب متاح مجانًا ويوصى به لهذا المقرر
- OCaml from the Very Beginning، الفصل 1، كتاب لطيف للغاية ويوصى به لهذا المقرر. ويتوفر الكتاب بصيغتي PDF وHTML مجانًا.
- جولة موجّهة [في OCaml]: الفصل 1 من Real World OCaml، وهو كتاب كتبه بعض أبناء كورنيل وقد يستمتع بعض الطلاب بقراءته
- تاريخ Standard ML: ورغم أنه يركّز على لهجة SML من لغة ML، فإنه ذو صلة بـ OCaml
- قيمة القيم (The value of values): محاضرة لمصمم Clojure (لهجة حديثة من Lisp) عن كيف مضى زمن البرمجة الأمرية
- علّم نفسك البرمجة في 10 سنوات: مقال لمدير أبحاث في Google يضع الوقت اللازم لتصبح مبرمجًا مثقفًا في المنظور الصحيح