لغات البرمجة · مترجَمة ومفسَّرة · الآلة الافتراضية · شِعر تعليمي

نَظمُ لغاتِ البرمجة

منظومةٌ للأديبة آمنة بنت أحمد بن ميرغني تصنّف لغات البرمجة وفقَ كيفية فهم الحاسوب للشِّفرة ومعالجتها - مترجَمة ومفسَّرة وهجينة - مع شرحٍ يكشف معانيها ويُذلّل سُبُل فهمها.

إ
منظومة: آمنة بنت أحمد بن ميرغني · شرح: إبراهيم أبو العنين
35 دقيقة قراءة
٠١

تقديمٌ ومنظومة

بسم الله الرحمن الرحيم

الحمدُ لله الذي علَّم بالقلم، علَّم الإنسانَ ما لم يعلم، فتح على عباده خزائنَ المعارف، وهداهم إلى صوغِ اللغاتِ والحروف، حتى أنشؤوا من العجائب آلاتٍ تفهمُ عن أهلها أمرَهم فتتمثَّل، وتنفِّذُ مرامَهم فتفعل.

وأشهدُ أن لا إلهَ إلا اللهُ وحدَه لا شريكَ له، علَّم آدمَ الأسماءَ كلَّها، وأشهدُ أنَّ سيدَنا ومولانا محمدًا عبدُه ورسولُه، أُوتيَ جوامعَ الكلم، صلَّى اللهُ عليه وعلى آلهِ وصحبِه وسلَّمَ تسليمًا كثيرًا.

أمَّا بعدُ: فقد وَقَعَتْ إليَّ منظومةٌ موجَزةُ المبنى، بديعةُ المعنى، نَظَمَتْها الأديبةُ الفاضلةُ آمنة بنتُ أحمدَ بنِ ميرغني - أدامَ اللهُ عليها نعمةَ البيان - سمَّتْها «نَظمَ تقسيمِ لغاتِ البرمجةِ وفقَ ترتيبِ الفَهمِ والمعالجة»، جَمَعَتْ فيها بين جَزالةِ العبارةِ العربيَّةِ ولُطفِ الاصطلاحِ الفنيِّ الحديثِ، فعرَّبَتْ بعضَ أسماءِ اللغاتِ بما يُوافِقُ معناها في لسانِها الأصليّ، وأبقَتْ بعضَها على لفظِه. فأحبَبْتُ أن أُقيِّدَ عليها تعليقةً مختصرةً تَكشفُ عن معانيها، وتُفصِحُ عن مراميها، وتُذلِّلُ على الطالبِ سُبُلَ فَهمِها، مع إشاراتٍ إلى ما تخلَّلَها من النُّكَتِ والفوائد.

في بحرِ المنظومة

المنظومةُ من بحرِ الرَّجَزِ الذي يُسمَّى «حمارَ الشعراء» لسهولةِ الرُّكوبِ عليه؛ وقد جرى اصطلاحُ أهلِ العلمِ على اختيارِه لمتونِ الفنونِ تقريبًا للحفظِ على الطلَّاب. ورتَّبَتْها الناظمةُ على مقدمةٍ يسيرةٍ، وثلاثةِ أبوابٍ: بابِ اللغاتِ المترجَمةِ، ثم المفسَّرةِ، ثم الهجينةِ، وخَتَمَتْ بخاتمةٍ ضمَّنَتْها البيتَ الأخير.

المنظومة كاملةً

الأبياتُ مُرقَّمةٌ للإحالةِ عليها في الشرح
١تقولُ بنتُ أحمدِ بنِ ميرغنيباسمِ الإلهِ المؤمنِ المُهيمنِ
٢في نَظمِ تقسيمِ لغاتِ البرمجةْوفقَ ترتيبِ الفَهمِ والمعالجةْ
بابُ اللغاتِ المترجَمة
٣أولى الأقسامِ منها ما يُترجَمُوذاكَ أنَّ كلَّ سطرٍ يُفهَمُ
٤ثُمَّ إنْ صحَّ الكلُّ التشغيلُ ابتدادفعةً واحدةً وإلا فلا
٥وذلكَ في سي وأختِها بداونحوِ هَسْكِلٍ وانطلِقْ والصَّدا
بابُ اللغاتِ المفسَّرة
٦ثانيهما ما فُسِّرَ من اللغاتْفي الفَهْمِ والتنفيذِ متعاقباتْ
٧إنْ صحَّ سطرٌ نَفَّذَ ثُمَّ انتقَلْإلى ما بعدَهُ وهكذا فَعَلْ
٨فإنْ يجدْ خلالَ السيرِ خطأًيقِفْ وثَمَّ لا يَضُرُّ ما مضى
٩وذاكَ في الياقوتِ والثعبانِوالجافاسكريبتِ فاستمعْ بياني
بابُ اللغاتِ الهجينة
١٠من اللغاتِ أيضًا قسمٌ افترقْوما لهُ من موضعٍ فيما سبقْ
١١تُترجَمُ السطورُ فيهِ أولافي خطوةٍ من قبلِ أن تُشغَّلا
١٢وبعدَ ذلكَ التفسيرُ يُبتدالكلِّ سطرٍ مفردٍ كما مضى
١٣مثالُهُ جافا وسي الموسومةْكوتلِنْ وهذا خَتمُ ذي المنظومةْ

شرحُ الديباجة

قولُها - رحمَها اللهُ ونفعَ بها -:

باسمِ الإلهِ المؤمنِ المُهيمنِمطلعُ المنظومة

أقول: افتتحَتْ منظومَتَها بالتبرُّكِ باسمِ اللهِ تعالى، جَرْيًا على سُنَّةِ أهلِ العلمِ في افتتاحِ مصنَّفاتِهم، اقتداءً بفاتحةِ الكتابِ العزيز. واختصَّتْ من أسماءِ اللهِ الحسنى اسمَين: «المؤمنَ» و«المُهيمنَ»، وهما الواردانِ في قولِه تعالى: ﴿الْمُؤْمِنُ الْمُهَيْمِنُ﴾ ١. فالمؤمنُ هو الذي يُؤمِنُ عبادَه من عذابِه ويُصدِّقُ رُسُلَه بما أظهرَ لهم من الآيات، والمُهيمنُ هو الرقيبُ الحافظُ لكلِّ شيءٍ، الشاهدُ على خلقِه بأعمالِهم.

نكتةٌ في تخصيصِ الاسمَين

وفي تخصيصِ هذين الاسمَين مع هذا الفنِّ بالذاتِ نكتةٌ حسنة؛ فصنعةُ البرمجةِ تَحتاجُ إلى «أمانةٍ» في بناءِ ما ينفعُ الخلقَ لا ما يَضُرُّهم، وإلى «هَيمنةٍ» على ما يُصنَع فلا يَسرَحُ ولا يَمرَح. وفيه إشارةٌ حسنةٌ إلى مجانبةِ ما سُمِّيَ بـ«البرمجةِ بالإحساس» (vibe coding)، حيث يكتبُ المرءُ شِفرةً لا يَفهمُها ولا يُهيمنُ عليها. واللهُ أعلم.

وقولُها: «في نَظمِ تقسيمِ لغاتِ البرمجةْ» بيانٌ لموضوعِ المنظومةِ، وهو تقسيمُ اللغاتِ البرمجيةِ باعتبارٍ مخصوص. وقولُها: «وفقَ ترتيبِ الفَهمِ والمعالجةْ» بيانٌ للمسلكِ الذي سَلَكَتْه في التقسيم؛ إذ ليس التقسيمُ عندها باعتبارِ القِدَمِ والحداثة، ولا باعتبارِ المجالاتِ التي تُستخدَمُ فيها، وإنما باعتبارِ كيفيَّةِ فَهمِ الحاسوبِ للشِّفرةِ المكتوبةِ ومعالجتِه إيَّاها؛ فـ«الفَهمُ» هو ما يُعبَّرُ عنه بـ(parsing / compilation)، و«المعالجةُ» هي التنفيذُ (execution).

فائدة
جَعْلُها الفَهمَ أوَّلًا ثم المعالجةَ جارٍ على الترتيبِ الطبيعيِّ، إذ لا مُعالَجةَ بلا فَهمٍ سابق، كما أنَّ العاقلَ لا يَعمَلُ بشيءٍ حتى يَفهَمَه، ومن حُفِظَ من العفنِ الذهنيِّ لا يكتبُ شِفراتٍ لا يَفهمُها ولا يستطيعُ الدفاعَ عنها في مراجعةِ الأكواد.

٠٢

بابُ اللغاتِ المترجَمة

قولُها:

الأبيات ٣ - ٥
٣أولى الأقسامِ منها ما يُترجَمُوذاكَ أنَّ كلَّ سطرٍ يُفهَمُ
٤ثُمَّ إنْ صحَّ الكلُّ التشغيلُ ابتدادفعةً واحدةً وإلا فلا
٥وذلكَ في سي وأختِها بداونحوِ هَسْكِلٍ وانطلِقْ والصَّدا

شَرَعَتْ في ذكرِ الأقسامِ فبدأتْ بالقسمِ الأولِ، وهو «اللغاتُ المترجَمة» (compiled languages). والترجمةُ في الأصلِ اللغويِّ نَقْلُ الكلامِ من لسانٍ إلى لسان، ثم استُعمِلَتْ في الاصطلاحِ البرمجيِّ في نَقْلِ الشِّفرةِ المصدريَّةِ (source code) المفهومةِ للإنسانِ إلى لُغةِ الآلةِ (machine code) المفهومةِ للحاسوب، بوساطةِ برنامجٍ يُسمَّى «المترجِمَ» (compiler).

وبيانُ عملِه ما قالَتْه في البيتِ الثاني: أنه يَمُرُّ على جميعِ السطورِ فيفحصُها سطرًا سطرًا، فإن وَجَدَ في أحدِها خطأً وقفَ ولم يُكمِل، ولم يَنتُج عنه برنامجٌ يُشغَّل. أما إن صحَّ الكلُّ فإنَّه يُنتِجُ ملفًّا تنفيذيًّا (executable) يُشغَّلُ دَفعةً واحدةً، لا سطرًا بعدَ سطر.

ثمرتانِ من هذا المسلك

الأولى: أنَّ الخطأَ في اللغاتِ المترجَمةِ يُكتَشَفُ «قبلَ» التشغيلِ لا أثناءَه، وهذه من مزاياها الكبرى؛ إذ لا يَقِفُ البرنامجُ في وجهِ المستخدِمِ وهو يعمل، بل إن كانت فيه عِلَّةٌ ظَهَرَت في طَورِ التطويرِ قبلَ أن تَصِلَ إلى يدِ المستخدِم.

الثانية: أنَّ اللغاتِ المترجَمةَ أسرعُ في التنفيذِ غالبًا، إذ لا يُحتاجُ حين التشغيلِ إلى فَهمٍ جديد؛ فقد فُرِغَ منه قبلَه. وكذلك الشِّفرةُ المفهومةُ من صاحبِها أسهلُ في الاعتناءِ، ولا تَحتاجُ إلى الاستذكاءِ في فهمِها كلَّ مرَّة.

كيف تُعالَجُ الشِّفرةُ في الأقسامِ الثلاثة
الشِّفرة المصدرية
مسار المعالجة

ثم مَثَّلَتْ لهذا القسمِ بخمسٍ من اللغاتِ الشهيرة:

  • سي: وهي لغةُ C المعروفةُ، وَضَعَها دِنِيس رِيتشي في مختبراتِ «بِل» سنةَ اثنتَين وسبعينَ وتسعِ مئةٍ وألفٍ من الميلاد، وعليها قامَتْ أنظمةُ التشغيلِ كـ«يونكس» و«لينكس».
  • أختُها: أي ++C التي طوَّرها بْيارِني سترُوسترُوب على أساسِ C، فكانَتْ بمنزلةِ الأختِ الشقيقةِ لها. ووصفُ علامةِ ++ بديعٌ كما يظهرُ للطالب، ويظهرُ لي أنَّ لفظَ «بنت» يكونُ حسنًا أيضًا، لأنَّ الابنَ - كما هو معلوم - امتدادُ أبيه.
  • هَسكل: وهي Haskell، لغةٌ وظيفيةٌ خالصة (purely functional)، سُمِّيَتْ على اسمِ المنطقيِّ هَسكل كاري.
  • انطَلِقْ: تعريبٌ للغةِ Go، إذ «go» في الإنجليزيَّةِ بمعنى اذهَبْ وانطلِقْ، وهي من وَضعِ مهندسي شركةِ «جوجل».
  • الصَّدا: تعريبٌ للغةِ Rust، إذ «rust» في الإنجليزيَّةِ هو الصَّدَأُ الذي يعتري الحديد.
نكتةٌ في صنيعِها
راعَتْ في التعريبِ «معنى» اللفظِ الأصليِّ لا مجرَّدَ لفظِه، فأخرجَتِ المعنى محفوظًا في ثوبٍ عربيٍّ بهيج، وهذا من لطيفِ اختيارِها.

٠٣

بابُ اللغاتِ المفسَّرة

قولُها:

الأبيات ٦ - ٩
٦ثانيهما ما فُسِّرَ من اللغاتْفي الفَهْمِ والتنفيذِ متعاقباتْ
٧إنْ صحَّ سطرٌ نَفَّذَ ثُمَّ انتقَلْإلى ما بعدَهُ وهكذا فَعَلْ
٨فإنْ يجدْ خلالَ السيرِ خطأًيقِفْ وثَمَّ لا يَضُرُّ ما مضى
٩وذاكَ في الياقوتِ والثعبانِوالجافاسكريبتِ فاستمعْ بياني

القسمُ الثاني هو «اللغاتُ المفسَّرة». وتسميتُها بـ«المفسَّرة» على معنى أنَّ «المفسِّرَ» (interpreter) يأخذُ السطرَ فيَفهمُه ثم يُنفِّذُه على الفور، ثم ينتقلُ إلى ما بعدَه؛ فهو يَجمَعُ بين الفَهمِ والتنفيذِ معًا، بخلافِ المترجِمِ الذي يَفصِلُ بينهما. وقولُها «متعاقبات» حالٌ تدلُّ على أنَّ الفَهمَ والتنفيذَ يأتيانِ متلاحقَين سطرًا إثْرَ سطر.

تحقيقٌ مهمٌّ في الأخطاء

ظاهرُ كلامِ الناظمةِ أنَّ المترجَمَ إمَّا أن يعملَ كاملًا أو لا يعملَ أصلًا، وأنَّ المفسَّرَ يَقِفُ حيثُ يجدُ الخطأَ دونَ أن يَضُرَّ ما مضى؛ وهذا الفارقُ إنَّما يَصِحُّ في نوعٍ مخصوصٍ من الأخطاءِ، لا في كلِّ خطأٍ مطلقًا. وبيانُه أنَّ الأخطاءَ على ضَربَين:

الضربُ الأوَّل: أخطاءُ الفَهمِ السابقةُ على التشغيل (compile-time errors)، كخطأٍ في تركيبِ الجملةِ (syntax error)، أو في تطابُقِ الأنواعِ (type mismatch)، أو في اسمِ دالَّةٍ لا وجودَ لها.

الضربُ الثاني: أخطاءُ التشغيلِ الطارئةُ وقتَ العمل (run-time errors)، كقِسمةٍ على صفر، أو تجاوزٍ لحدودِ مَصفوفة، أو محاولةِ فتحِ ملفٍّ غيرِ موجود، أو انقطاعٍ في الشبكة، أو نَفادِ الذاكرة، أو إشارةٍ إلى عُنوانٍ خاوٍ (null pointer).

فالفارقُ الذي بيَّنَتْه الناظمةُ إنَّما يَصِحُّ في الضربِ الأولِ؛ فالمترجِمُ يَكشِفُه دَفعةً واحدةً قبلَ التشغيل، والمفسِّرُ لا يَكشِفُه إلَّا حين يَبلُغَ السطرَ المعتلَّ. أمَّا الضربُ الثاني فيَقَعُ في القسمَين سواءً بسواء؛ إذ لو كان برنامجُك المترجَمُ بـ«سي» قد فَتَحَ ملفًّا وكتبَ فيه في السطرِ الأوَّل، ثم قَسَمَ على صفرٍ في السطرِ العاشر، فالملفُّ قد كُتِبَ ولا يَرتدُّ، وحصلَ الوقوفُ في موضعِ الخطأِ تمامًا كما يَحصُلُ في المفسَّر. وقد يُفضي مثلُ هذا في بعضِ اللغاتِ إلى ما يُسمَّى (segmentation fault) وهو أسوأُ من خطأِ المفسَّرِ في كثيرٍ من الأحوال.

فاحفَظْ هذا التحقيقَ فإنَّه موضعُ إشكالٍ على كثيرٍ من المبتدئين، يظنُّون أنَّ المترجَمَ إذا نجحَ في ترجمتِه سَلِمَ البرنامجُ من كلِّ خطأٍ، وليس الأمرُ كذلك.

فصلٌ في تقسيمِ اللغاتِ باعتبارِ الأنواع

ومِن المناسبِ هنا ذِكرُ تقسيمٍ آخرَ مُستقلٍّ عن تقسيمِ الناظمةِ، وهو تقسيمُها باعتبارِ «الأنواع» (types). واعلَمْ أنَّ كلَّ قيمةٍ في البرمجةِ لها نوعٌ تَنتَمي إليه: إمَّا عددٌ صحيحٌ (integer)، أو عددٌ كسريٌّ (float)، أو نصٌّ (string)، أو قيمةٌ منطقيَّةٌ (boolean)، أو غيرُ ذلك من الأنواعِ المركَّبة. واللغاتُ في معامَلَتِها للأنواعِ على قسمَين:

محقِّقةٌ للأنواعِ وقتَ الترجمة (static)محقِّقةٌ للأنواعِ وقتَ التشغيل (dynamic)
يُصرِّحُ المبرمِجُ بنوعِ كلِّ متغيِّر، ويَكشِفُ المترجِمُ خطأَ النَّوعِ قبلَ التشغيل.لا يُصرَّحُ فيها بالنوع، ولا يظهرُ خطأُ النوعِ إلَّا حين يبلغَ المفسِّرُ السطرَ المعتلَّ.
سي، وأختُها، وهَسكل، وانطَلِق، والصَّدا، وجافا، وسي الموسومة، وكوتلِن.الياقوتُ، والثعبانُ، والجافاسكريبت.
تقريرٌ نفيس
هذا التقسيمُ مستقلٌّ عن تقسيمِ الناظمةِ، فلا يَلزمُ من كَوْنِ اللغةِ مترجَمةً أن تكونَ ساكنةَ النوع، ولا من كَوْنِها مفسَّرةً أن تكونَ متحرِّكةَ النوع، وإن كان الغالبُ اقترانَ هذا بذاك. ومِثالُ الافتراقِ: «تايبسكريبت» (TypeScript) وهي نُسخةٌ ساكنةُ النوعِ من الجافاسكريبتِ المفسَّرة.
نكتةٌ خَفيَّةٌ في حقيقةِ «الثعبان» و __pycache__

في تمثيلِ الناظمةِ للمفسَّرةِ بـ«الثعبان» (Python) مسامحةٌ لطيفةٌ؛ فالثعبانُ ليس مفسَّرًا محضًا على الحقيقة، بل يَسلُكُ مسلكًا قريبًا من مسلكِ الهجينةِ في بعضِ وجوهه. وذلك أنَّ مفسِّرَ الثعبانِ الرسميَّ (CPython) إذا مَرَّ على ملفِّ الشِّفرةِ لا يُفسِّرُ السطورَ رأسًا من نصِّها كما يَتوهَّمُ كثيرٌ من الناس، بل يُترجِمُها أولًا إلى صيغةٍ وسيطةٍ تُسمَّى «البايتكود» (bytecode)، ثم يُنفِّذُ هذا البايتكود في آلةٍ افتراضيةٍ داخلَه، شأنَ الهجينةِ. والفارقُ بينه وبين «جافا» أنَّ هذه الترجمةَ في الثعبانِ تَقَعُ عند كلِّ تشغيلٍ تلقائيًّا، لا في خطوةٍ سابقةٍ يأمُرُ بها المبرمِج.

ثم إنَّ الثعبانَ يَحفَظُ هذا البايتكودَ في مُجلَّدٍ يُسمَّى «__pycache__» (أي المَخبَأ)، تَجِدُه ينشأُ تلقائيًّا بجانبِ ملفَّاتِك بصيغةِ (.pyc). وفائدةُ هذا الحَفظِ أنَّه إذا أُعيدَ تشغيلُ البرنامجِ ولم يُعَدَّلِ الملفُّ الأصليُّ، أخذَ المفسِّرُ البايتكودَ الجاهزَ مباشرةً دونَ إعادةِ الترجمة، فوُفِّرَ من الزمنِ ما وُفِّر. فإذا رأيتَ هذا المجلَّدَ ينشأُ بجانبِ ملفَّاتِك فاعلَمْ أنَّه أثرُ هذا الصنيعِ لا عِلَّةٌ يجبُ إزالتُها، ويَصلُحُ استثناؤُه من أنظمةِ الإصدار (git).

وهذا المسلكُ نفسُه تَسلُكُه «الياقوتُ» في آلتها الافتراضيةِ المسمَّاةِ YARV منذ الإصدارِ التاسعِ من المئةِ الثالثةِ بعد الألفَين، و«الجافاسكريبت» في محرِّكاتِها الحديثةِ كمحرِّكِ V8 المستخدَمِ في متصفِّحِ كروم. بل تَتَجاوَزُ الجافاسكريبتُ ذلك إلى ما يُسمَّى «الترجمةَ في الطَّيَران» (JIT - Just-In-Time)، وهي ترجمةُ البايتكودِ الحارِّ المتكرِّرِ تنفيذُه كثيرًا إلى لغةِ الآلةِ مباشرةً وقتَ التشغيلِ طلبًا للسُّرعةِ القُصوى.

فائدة
من ثمراتِ مسلكِ التفسيرِ سرعةُ التطويرِ، إذ يستطيعُ المبرمِجُ أن يُجَرِّبَ سطرًا سطرًا دونَ انتظارِ ترجمةِ الكلِّ، ويَستعمِلَ ما يُسمَّى «قوقعةَ الحوار» (REPL) ليُجَرِّبَ التعبيراتِ مباشرة. ومن مَضارِّه بُطءُ التنفيذِ نسبيًّا، لأنَّ الفَهمَ يُعادُ - ولو جُزئيًّا - في كلِّ تشغيل، وعدمُ كشفِ أخطاءِ النوعِ مبكِّرًا في اللغاتِ المتحرِّكةِ النوع.

ثم مثَّلَتْ لهذا القسمِ بثلاثٍ:

  • الياقوت: تعريبٌ للغةِ Ruby، وهي في الأصلِ حجرٌ كريمٌ أحمرُ، وَضَعَها يوكيهيرو ماتسوموتو.
  • الثعبان: تعريبٌ للغةِ Python، وَضَعَها خيدو فان روسُّم وسمَّاها على اسمِ فرقةٍ كوميديَّةٍ لا على اسمِ الحيَّةِ، لكنَّ الاسمَ صار عَلَمًا على الحيَّةِ في الأذهان، فأخذَتْ هي بذلك.
  • الجافاسكريبت: وأبقَتْها على لفظِها لشهرتِها به، وهي لغةُ المتصفِّحاتِ، وَضَعَها برَندان آيك.

وقولُها: «فاستَمِعْ بياني» تَشويقٌ لما بعدُ، من بابِ حُسنِ التخلُّصِ من بابٍ إلى باب، وهو من محاسنِ النَّظم.


٠٤

بابُ اللغاتِ الهجينة

قولُها:

الأبيات ١٠ - ١٢
١٠من اللغاتِ أيضًا قسمٌ افترقْوما لهُ من موضعٍ فيما سبقْ
١١تُترجَمُ السطورُ فيهِ أولافي خطوةٍ من قبلِ أن تُشغَّلا
١٢وبعدَ ذلكَ التفسيرُ يُبتدالكلِّ سطرٍ مفردٍ كما مضى

هذا هو القسمُ الثالثُ، وهو الذي جَمَعَ بين خَصيصَتَي القسمَين السابقَين، فسُمِّيَ «الهجينَ» (hybrid) لأنه مُركَّبٌ من أصلَين. والهجينُ في اللغةِ العربيةِ هو المولَّدُ من أبوَينِ مختلفَي الأصل. وقولُها: «قسمٌ افترَقْ» أي انفصَلَ عن القسمَين السابقَين وتَميَّزَ بخاصِّيَّةٍ لا تجتمعُ فيهما، وقولُها: «وما له من موضعٍ فيما سبَقْ» تأكيدٌ لهذا التمايز.

ثم بيَّنَتْ آليَّتَه: أنَّ الشِّفرةَ تُترجَمُ أولًا إلى صِيغةٍ وسيطةٍ - لا إلى لغةِ الآلةِ المحضةِ - تُسمَّى «بايتكود» (bytecode)، ثم يأتي دورُ الآلةِ الافتراضيةِ (virtual machine) فتَقرأ هذا البايتكود سطرًا سطرًا، كما يَصنعُ المفسِّرُ في المفسَّرةِ تمامًا.

فصلٌ في حقيقةِ الآلةِ الافتراضيةِ وأقسامِها

اعلَمْ - أرشَدَكَ اللهُ - أنَّ لفظَ «الآلةِ الافتراضيَّةِ» (virtual machine) يُطلَقُ في الصناعةِ على معنيَيْنِ متغايرَيْنِ، وقد يَلتبِسُ أحدُهما بالآخرِ على غيرِ المتخصِّص:

  • آلاتُ النظامِ (system VMs): وهي برامجُ تُحاكي جهازًا كاملًا بأعتُدِه من معالِجٍ وذاكرةٍ وأقراصٍ، حتى يُمكِنَ تنصيبُ نظامِ تشغيلٍ بتمامِه داخلَها، ومنها VMware وVirtualBox وKVM وHyper-V. وليس مرادُ الناظمةِ هذا القسمَ.
  • آلاتُ العمَليَّةِ (process VMs): وهي التي يَدورُ عليها كلامُ الناظمةِ، ووَظيفتُها تشغيلُ «برنامجٍ واحدٍ» مكتوبٍ بصيغةِ البايتكودِ، دونَ محاكاةِ جهازٍ كامل، ومثالُها JVM وCLR وBEAM.
تنبيه
فإذا ورَدَ في كلامِ الشُّرَّاحِ والمصنِّفين في هذا الفنِّ ذِكرُ «الآلةِ الافتراضيَّةِ» مُطلقًا، فالمرادُ القسمُ الثاني إلا بقرينة.
تحقيقٌ في بِنيَةِ هذه الآلات

وآلاتُ العمَليَّةِ تَنقَسِمُ باعتبارِ بِنيَتِها الداخليَّةِ إلى ضربَين:

(أ) الآلاتُ المَكدَسيَّةُ (stack-based): تَستعمِلُ «مَكدَسًا» (stack) لإجراءِ العمليَّاتِ، فتَدفَعُ إليه المُعامَلاتِ ثم تَسحبُها لإجراءِ الحساب. فإذا أردتَ جمعَ عددَين دَفَعتَهما إلى المكدَسِ ثم أمَرتَ بالجمعِ، فيَسحَبُهما ويَدفَعُ ناتجَهما. ومنها JVM في أشهرِ تنفيذاتِها، وCPython.

(ب) الآلاتُ السِّجِلِّيَّةُ (register-based): تُحاكي المعالِجاتِ الحقيقيَّةَ في استعمالِ «السجلَّاتِ» (registers) بدلَ المكدَس، فتكونُ أوامرُها أقلَّ عددًا وأقربَ إلى الآلةِ الفِعليَّة. ومنها Dalvik قديمًا، وART حديثًا في الأندرويد، وآلةُ لغةِ Lua.

فائدة
السِّجلِّيَّةُ أسرعُ غالبًا لأنَّ أوامرَها مُكَثَّفة، لكنَّ المَكدَسيَّةَ أيسَرُ في التوليدِ والنقل. ولذا كان اختيارُ JVM للمَكدَسيَّةِ سنةَ خمسٍ وتسعينَ من القرنِ الماضي وجيهًا في وقتِه؛ إذ كانت البساطةُ وقابليَّةُ النقلِ أَولى من السرعةِ المحضة.

فصلٌ في أشهرِ هذه الآلاتِ وأعظمِها شأنًا

  • آلةُ الجافا (JVM): أُمُّ البابِ وأصلُه، وَضَعَتْها شركةُ «صَن» سنةَ خمسٍ وتسعينَ من الميلاد، ولها تنفيذاتٌ متعدِّدة: HotSpot (تنفيذُ أوراكل المرجعيُّ، يرصُدُ «النقاطَ الحارَّةَ» فيُترجِمُها بتقنيَّةِ JIT)، وOpenJ9 (من IBM، يَمتازُ بخِفَّةِ الذاكرة)، وGraalVM التي تَجمَعُ بين JIT والترجمةِ المُسْبَقةِ التامَّةِ (AOT)، فتستطيعُ إخراجَ برنامجِك ملفًّا تنفيذيًّا مستقلًّا عن JVM بالكُليَّة.
  • زمنُ التشغيلِ العامُّ (CLR): آلةُ مايكروسوفت الافتراضيَّةُ لبيئةِ NET.، أُطلِقَتْ سنةَ ألفَينِ من الميلاد، مُصمَّمةٌ لِتَستَقبِلَ بايتكودَ «أكثرَ من لغةٍ» في وقتٍ واحد (C# وVB.NET وF#)، وتَستَعمِلُ صيغةً وسيطةً تُسمَّى CIL.
  • آلةُ بِيم (BEAM): تَعمَلُ عليها لغتا Erlang وElixir، وامتازَتْ بِدَعمٍ فائقٍ لـ«العمَليَّاتِ المتوازيَةِ»؛ إذ تَستَطيعُ حَملَ ملايينِ العمَليَّاتِ الخفيفةِ في الوقتِ الواحد، وعليها قامَتْ أنظمةُ الاتصالاتِ الضخمة.
  • دالفيك ثم ART: آلتانِ وَضَعَتْهما جوجل لتشغيلِ تطبيقاتِ الأندرويد، كانت دالفيك سجِلِّيَّةً، ثم جاءَتْ ART فَغَلَّبَتِ الترجمةَ المسبقةَ توفيرًا للطاقة.
  • آلةُ ويبأسمبلي (WebAssembly): آلةٌ مَكدَسيَّةٌ تَعمَلُ في المتصفِّحات، تستقبلُ بايتكودًا مُنتَجًا من Rust أو ++C أو Go، فيَعمَلُ بسرعةٍ قريبةٍ من سرعةِ الآلةِ الأصليَّة.
تنبيهٌ في الفرقِ بين JVM وCLR
مع تَقاربِهما في المقصدِ، بينهما فروقٌ لطيفة: أنَّ CLR بُنِيَتْ على نيَّةِ تعدُّدِ اللغاتِ من الابتداء، بخلافِ JVM التي جاءَ تعدُّدُ لغاتِها بعدَ حين؛ وأنَّ CLR تَدعَمُ «الأنواعَ القيميَّةَ» (value types) دعمًا أصيلًا، بخلافِ JVM التي لم تَكُنْ تَعرفُ إلا المراجعَ إلى عهدٍ قريب.
تحقيقٌ مهمٌّ في حَدِّ «الهجين»

في كلامِ الناظمةِ موضعُ نَظَرٍ يَحسُنُ التنبيهُ عليه، وهو زَعمُها أنَّ هذا القسمَ «افترَقَ عمَّا سَبَق، وما له من موضعٍ فيما سبَق». والتحقيقُ أنَّ الصناعةَ ليست كذلك على الإطلاق، من وجوه:

الأول: أنَّ الثعبانَ والياقوتَ وسائرَ ما جُعِلَ من المفسَّرةِ تَستَعمِلُ في حقيقتِها آلاتٍ افتراضيَّةً وبايتكودًا، فهي هجينةٌ بالحقيقة.

الثاني: أنَّ كثيرًا مما عُدَّ من الهجينةِ - كـJava وKotlin وC# - يُمكِنُ ترجمتُه اليومَ ترجمةً مُسبَقةً تامَّةً (AOT) فيَصيرُ مترجَمًا صِرفًا، كما في GraalVM Native Image وKotlin/Native وNET. Native AOT؛ وفي هذا نقضٌ لطيفٌ لحدِّ الناظمةِ فتأمَّل.

الثالث: أنَّ بعضَ ما عُدَّ من المترجَمةِ - كـHaskell - له تنفيذٌ هجينٌ أيضًا (GHCi) يُفسِّرُه سطرًا سطرًا.

فَظَهَرَ أنَّ التقسيمَ الثلاثيَّ هو باعتبارِ «الحالةِ الغالبةِ والتنفيذِ الأشهرِ» لا باعتبارِ حقيقةِ اللغةِ نفسِها. ولا تَثريبَ على الناظمةِ في ذلك، إذ الأنظامُ التعليميَّةُ تُبنى على الغالبِ لا على النوادر؛ وإنما هذا تنبيهٌ للمتقدِّمِ في الفنِّ حتى لا يَعتَقِدَ التقسيمَ حقيقةً ثابتةً على الإطلاق.

تنبيهٌ نفيس: مزايا مسلكِ الآلةِ الافتراضية

يَجمعُ هذا المسلكُ بين مَزِيَّاتٍ جليلة: قابليَّةُ النقلِ (portability) بين الأجهزةِ المختلفةِ، فالبايتكودُ واحدٌ والأجهزةُ متعدِّدة، وهي التي عبَّرَتْ عنها جافا بشعارِها «اكتُبْ مرَّةً تَعمَلْ في كلِّ مكان»؛ وكَشْفُ كثيرٍ من الأخطاءِ قبلَ التشغيلِ كما في المترجَمة؛ وإدارةُ الذاكرةِ التلقائيَّةُ (garbage collection) فلا يَحتاجُ المبرمجُ إلى تحريرِ الذاكرةِ بيدِه كما في C؛ والأمانُ (sandboxing) إذ تُقيِّدُ الآلةُ ما يَفعلُه البرنامجُ فلا يَنفُذُ إلى مواردِ الجهازِ إلا بإذن.

ثم مثَّلَتْ له بثلاث:

  • جافا (Java): وَضَعَها جيمس جوسلِنغ في شركةِ «صَن مايكروسيستمز»، وهي أشهرُ لغاتِ هذا القسمِ، تَعمَلُ على JVM، واستحوذَتْ عليها شركةُ أوراكل سنةَ عشرٍ من الألفيَّةِ الثانية.
  • سي الموسومة (#C): سُمِّيت بـ«الموسومةِ» لأنَّ اسمَها الأصليَّ C مع علامةِ (#) التي تُنطَقُ «شارب»، والوَسمُ هو العلامة، وَضَعَتْها مايكروسوفت بقيادةِ أندرس هيلسبرغ، تعملُ على CLR.
  • كوتلن (Kotlin): وَضَعَتْها شركةُ «JetBrains»، وتَعمَلُ على JVM أصالةً، وقد صارَتْ من أشهرِ لغاتِ تطبيقاتِ الأندرويدِ بإعلانِ جوجل تبنِّيَها سنةَ سبعَ عشرةَ من الألفيَّة، ولها تنفيذاتٌ أُخَرُ تَنقلُها إلى JavaScript وإلى شِفرةِ الآلةِ المحضَّةِ (Kotlin/Native).

٠٥

الخاتمةُ وخلاصة

قولُها:

البيت ١٣
١٣مثالُهُ جافا وسي الموسومةْكوتلِنْ وهذا خَتمُ ذي المنظومةْ

وقولُها: «وهذا خَتمُ ذي المنظومةْ» إعلانٌ منها بانتهاءِ النَّظمِ على عادةِ الناظِمين، وهو من قبيلِ «حُسنِ الخِتام»، كما اعتَنَوا بـ«حُسنِ الابتداء».

خلاصةٌ محرَّرة

فالتقسيمُ الثلاثيُّ الذي اختارَتْه الناظمةُ - مترجَمةٌ، ومفسَّرةٌ، وهجينةٌ - تقسيمٌ تعليميٌّ حَسَنٌ يُقرِّبُ المعنى على الطالب، والحقُّ أنَّ كلَّ اللغاتِ الحديثةِ تَسلُكُ مسالكَ مركَّبةً بين الترجمةِ والتفسيرِ والتحسينِ في الطَّيَران؛ وإنَّما تُنسَبُ كلُّ لغةٍ إلى القسمِ الذي يَغلِبُ على ظاهرِ تعامُلِ المبرمِجِ معها. واللهُ أعلمُ بالصواب.

خريطةُ اللغاتِ الواردةِ في المنظومة
اضغطْ على القسمِ لتصفيةِ اللغات. كلُّ بطاقةٍ تُبيِّنُ التعريبَ والاسمَ الأصليَّ والنوعَ وواضِعَها.

هذا آخرُ ما تَيَسَّرَ من شرحِ هذه المنظومةِ اللطيفةِ، جاهدتُ فيه أن أكونَ مع الناظمةِ لا عليها، ومع المرادِ لا بعيدًا عنه؛ فما كان فيه من صوابٍ فمن اللهِ وحدَه، وما كان من خطأٍ أو قصورٍ فمن نفسي والشيطانِ، واللهُ ورسولُه منه بريئان.


ملحق

معجم المصطلحات

بالعربيةبالإنجليزيةالمعنى باختصار
اللغات المترجَمةCompiled Languagesلغات تُترجَم شفرتها كاملةً إلى لغة الآلة قبل التشغيل، فإن صحّ الكل أُنتج ملف تنفيذي. مثل C وRust.
اللغات المفسَّرةInterpreted Languagesلغات يأخذ مفسِّرها السطر فيفهمه ثم ينفذه على الفور سطرًا إثر سطر. مثل Ruby وPython.
اللغات الهجينةHybrid Languagesلغات تُترجَم أولًا إلى بايتكود ثم تنفّذه آلة افتراضية سطرًا سطرًا. مثل Java وC#.
المترجِمCompilerبرنامج ينقل الشفرة المصدرية إلى لغة الآلة دفعةً واحدة، ويكشف أخطاء الفهم قبل التشغيل.
المفسِّرInterpreterبرنامج يجمع الفهم والتنفيذ معًا، يأخذ السطر فيفهمه فينفذه ثم ينتقل لما بعده.
الشفرة المصدريةSource Codeالنص الذي يكتبه المبرمج بلغة مفهومة للإنسان قبل ترجمته أو تفسيره.
لغة الآلةMachine Codeالتعليمات الثنائية التي يفهمها المعالج مباشرة وينفذها.
الملف التنفيذيExecutableناتج الترجمة في اللغات المترجَمة، يُشغَّل دفعةً واحدة لا سطرًا بعد سطر.
البايتكودBytecodeصيغة وسيطة بين الشفرة المصدرية ولغة الآلة، تقرأها الآلة الافتراضية. تستعملها Java وPython.
الآلة الافتراضيةVirtual Machineبرنامج ينفّذ البايتكود. منها آلات النظام (تحاكي جهازًا كاملًا) وآلات العملية (تشغّل برنامجًا واحدًا) كـJVM.
الترجمة في الطيرانJIT (Just-In-Time)ترجمة البايتكود الحارّ المتكرّر تنفيذه إلى لغة الآلة وقت التشغيل طلبًا للسرعة.
الترجمة المسبقةAOT (Ahead-Of-Time)ترجمة تامة قبل التشغيل تُحوّل اللغة الهجينة إلى مترجَمة صرفة، كما في GraalVM Native Image.
الأنواع الساكنةStatic Typingالتصريح بنوع كل متغير وكشف خطأ النوع قبل التشغيل. كما في C وHaskell وJava.
الأنواع المتحركةDynamic Typingلا يُصرَّح فيها بالنوع، ولا يظهر خطأ النوع إلا وقت بلوغ السطر المعتل. كما في Python.
خطأ وقت الترجمةCompile-time Errorخطأ الفهم السابق على التشغيل، كخطأ التركيب (syntax) أو تطابق الأنواع.
خطأ وقت التشغيلRun-time Errorخطأ طارئ أثناء العمل، كقسمة على صفر أو تجاوز حدود مصفوفة أو إشارة خاوية.
قوقعة الحوارREPLبيئة تفاعلية تتيح تجربة التعبيرات سطرًا سطرًا دون ترجمة الكل، من ثمرات التفسير.
كنس المهملاتGarbage Collectionإدارة الذاكرة تلقائيًا فلا يحتاج المبرمج إلى تحريرها بيده، من مزايا الآلة الافتراضية.
المكدَسStack-based VMآلة افتراضية تجري العمليات عبر مكدس تدفع إليه المعاملات وتسحبها. كـJVM وCPython.
السِّجِلّاتRegister-based VMآلة افتراضية تحاكي المعالجات في استعمال السجلات، أوامرها أقل وأقرب للآلة. كـDalvik وLua.
عطل التجزئةSegmentation Faultعطل وقت تشغيل في بعض اللغات عند الوصول لذاكرة غير مسموحة، من أسوأ أخطاء التشغيل.
اقرأ أيضا