اذهب إلى المحتوى

سننظر في كيفية دعم بايثون لأسلوب آخر من أساليب البرمجة، ألا وهو البرمجية الوظيفية Functional Programming، واختصارًا FP، وهو موضوع متقدم بالنسبة للمبتدئين في البرمجة، كما ذكرنا في شأن التعاودية في المقال السابق، وربما تصرف نظرك عنه الآن إلى أن تقطع شوطًا في البرمجة بنفسك.

يعتقد المؤيدون للبرمجة الوظيفية أنها الأسلوب الأمثل لتطوير البرمجيات.

سنغطي في هذا المقال ما يلي:

  • الفرق بين أسلوب البرمجة التقليدي والأسلوب الدالّي في البرمجة.
  • الدول والتقنيات الخاصة بالبرمجية الوظيفية في بايثون.
  • دوال لامدا Lambda Functions.
  • تقييم الدارة المقصورة البولياني Short Circuit Boolean evaluation، والتعابير الشرطية.
  • البرامج كتعابير.

ما هي البرمجية الوظيفية؟

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

وتدور البرمجية الوظيفية حول التعابير، بل يمكن القول إن البرمجية الوظيفية هي البرمجة تعبيرية التوجه expression oriented programming، لأن كل شيء فيها يؤول إلى تعبير في النهاية، وقد ذكرنا أن التعبير هو تجميعة من العمليات والمتغيرات التي ينتج عنها قيمة واحدة، فيكون x == 5 تعبيرًا بوليانيًا boolean، و5 + (7-Y) تعبيرًا حسابيًا، و"Hello world".uppercase()‎ تعبيرًا نصيًا، وهذا التعبير الأخير هو استدعاء دالة function أيضًا، أو استدعاء تابع method بالأحرى، على كائن السلسلة النصية "Hello world"، وسنرى أهمية الدوال في البرمجية الوظيفية، كما هو واضح من الاسم.

تُستخدم الدوال في البرمجية الوظيفية مثل كائنات، أي أنها تُمرَّر من مكان لآخر داخل البرنامج بنفس طريقة تمرير المتغيرات، وقد رأينا أمثلةً على ذلك في برامج الواجهة الرسومية التي أنشأناها من قبل، حيث أسندنا اسم الدالة إلى سمة command الخاصة بمتحكم الزر، وعاملنا دالة معالج الحدث على أنها كائن وأسندنا إلى الزر مرجعًا إلى الدالة، وهذا المفهوم الخاص بتمرير الدوال في البرامج أمر أساسي في البرمجية الوظيفية، كما تميل البرامج الوظيفية إلى أن تكون قائمية التوجه List Oriented.

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

ويهَيكل البرنامج الوظيفي بتعريف تعبير يلتقط الهدف من البرنامج، وكل شرط term في التعبير هو تعليمة لخاصية من خصائص المشكلة -وربما يوضع الشرط نفسه في تعبير آخر-، ونحصل على الحل من خلال تقييم كل شرط من هذه الشروط.

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

اقتباس

موثوقية البرمجية الوظيفية

تأتي موثوقية البرامج الوظيفية من العلاقة الوطيدة بين البنى الخاصة بالبرمجية الوظيفية والمواصفات الاصطلاحية formal specification languages مثل: Z أو VDM، فإذا حُددت مشكلة في لغة رسمية فمن البديهي أن نترجم التحديد إلى لغة وظيفية التوجه مثل Haskell، لكن إذا كان التحديد خاطئًا فسيكون البرنامج الناتج مرآة لذلك الخطأ! يُعرف هذا المبدأ في علوم الحاسوب باسم "Garbage In, Garbage Out" أو "قمامة داخلة، قمامة ناتجة"، ولا تزال هذه الصعوبة في التعبير عن متطلبات النظام بأسلوب موجز لا لبس فيه من أعظم التحديات في هندسة البرمجيات.

كيف تنفذ بايثون البرمجية الوظيفية

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

سننظر الآن في بعض الدوال المتوفرة في بايثون ونرى كيف تعمل على بعض أمثلة هياكل البيانات التي نعرّفها على النحو التالي:

choices = ['eggs','chips','spam']
numbers = [1,2,3,4,5]

def spam(item): 
    return "Spam & " + item

الدالة map(aFunction, aSequence)‎

تطبق الدالة aFunction الخاصة ببايثون على كل عضو من aSequence، ويكون التعبير كما يلي:

L = map(spam, choices)
print( list(L) )

ينتج عن هذا إعادة قائمة جديدة في L، مع السابقة Spam &‎ في حالتنا قبل كل عنصر، ونلاحظ كيف مررنا الدالة spam()‎ إلى دالة map()‎ مثل قيمة، أي أننا لم نستخدم الأقواس لتنفيذ شيفرة الدالة، بل استخدمنا اسمها مرجعًا إلى الدالة، ولعلك تذكر أننا فعلنا هذا مع معالجات الأحداث في مقال برمجة الواجهات الرسومية، وهذه الخاصية في معاملة الدوال مثل قيم هي إحدى المزايا الرئيسية في البرمجية الوظيفية.

يمكن تحقيق نفس النتيجة بكتابة ما يلي:

L = []
for i in choices:
   L.append( spam(i) )
print( L )

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

الدالة (filter(aFunction, aSequence

تستخلص filter كل عنصر في التسلسل aSequence تعيد له الدالة aFunction القيمة True، وإذا عدنا إلى قائمة الأعداد الخاصة بنا فيمكن أن ننشئ قائمةً جديدةً من الأعداد الفردية فقط:

def isOdd(n): return (n%2 != 0) # mod استخدم العامل
L = filter(isOdd, numbers)
print( list(L) )

نلاحظ مرةً أخرى أننا نمرر اسم الدالة isodd إلى filter قيمة وسيط، بدلًا من استدعاء isodd()‎ مثل دالة، وعلى أي حال نستطيع كتابة الأسلوب البديل التالي:

def isOdd(n): return (n%2 != 0)
L = []
for i in numbers:
   if isOdd(i):
      L.append(i)
print( L )

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

توجد عدة أدوات أخرى للبرمجة الوظيفية في وحدة اسمها functools يمكن استيرادها وتصفحها في محث بايثون، ويُرجع إلى dir()‎ وhelp()‎ عند الحاجة.

الدالة لامدا Lambda

إحدى الخصائص الواضحة في الأمثلة السابقة هو أن الدوال التي مُرِّرت إلى دوال البرمجية الوظيفية كانت قصيرة جدًا، وغالبًا ما كانت سطرًا واحدًا فقط، وتوفر بايثون دعمًا جديدًا للبرمجة الوظيفية لتوفير الجهد المبذول لتعريف هذه الدوال الصغيرة، وهي دالة لامدا lambda، والتي يأتي اسمها من فرع في الرياضيات هو حسابات لامدا Lambda Calculus، الذي يستخدم حرف لامدا الإغريقي λ لتمثيل مفهوم قريب من هذا.

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

وتبدو الدالة لامدا بالشكل التالي:

lambda <aParameterList> : <a Python expression using the parameters>

وعلى ذلك يمكن كتابة دالة isodd سالفة الذكر كما يلي:

isOdd = lambda j: j%2 != 0

ونتجنب هنا تعريف السطر بالكامل من خلال إنشاء الدالة لامدا داخل الاستدعاء على filter كما يلي:

L = filter(lambda j: j%2 != 0, numbers)
print( list(L) )

ويُنفَّذ الاستدعاء إلى map باستخدام ما يلي:

L = map(lambda s: "Spam & " + s, choices)
print( list(L) )

ونلاحظ هنا أننا كنا نحول نتائج map()‎ وfilter()‎ إلى قوائم، لأنهما صنفان يعيدان نسخًا من شيء يدعى المتكرر itreable، وهو يتصرف مثل التسلسل أو التجميعة إذا استُخدم على حلقة تكرارية، ويمكن تحويله إلى قائمة، لكن كفاءته تظهر في استخدام الذاكرة، وصديقتنا القديمة range()‎ قابلة للتكرار كذلك، وتسمح بايثون بإنشاء أنواع قابلة للتكرار خاصة بنا، لكننا لن نشرحها.

استيعاب القوائم

استيعاب القوائم list comprehension هي تقنية لبناء قوائم جديدة، مستوردة من لغة Haskell وأُدخلت إلى بايثون في الإصدار الثاني، وبنيتها غريبة قليلًا وتشبه الصياغة الرياضية، مثلًا:

[<expression> for <value> in <collection> if <condition>]

والتي تكافئ ما يلي:

L = []
for value in collection:
    if condition:
        L.append(expression)

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

>>> [n for n in range(10) if n % 2 == 0 ]
[0, 2, 4, 6, 8]

والذي يقول إننا نريد قائمةً من القيم n التي تُختار من المجال 0-9، وتكون زوجيةً (n % 2 == 0)، ويمكن استبدال دالةٍ بالشرط الأخير لا شك، شرط أن تعيد الدالة قيمةً تستطيع بايثون أن تفسرها مثل قيمة بوليانية، وعليه يمكن إعادة كتابة المثال السابق كما يلي:

>>>def isEven(n): return ((n%2) == 0)
>>> [ n for n in range(10) if isEven(n) ]
[0, 2, 4, 6, 8]

والآن لننشئ قائمةً من تربيعات أول 5 أعداد:

>>> [n*n for n in range(5)]
[0, 1, 4, 9, 16]

نلاحظ أن تعليمة if الأخيرة لم تكن ضروريةً لكل حالة، فالتعبير الابتدائي هنا هو n*n، حيث نستخدم جميع قيم المجال، لنستخدم الآن تجميعةً موجودةً مسبقًا بدلًا من دالة المجال:

>>> values = [1, 13, 25, 7]
>>> [x for x in values if x < 10]
[1, 7]

يمكن استخدام ذلك لاستبدال دالة المرشح التالية:

>>> print( list(filter(lambda x: x < 10, values)) )
[1, 7]

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

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

بنى أخرى

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

التقييم المقصور أو تقييم الدارة المقصورة

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

>>> def TRUE():
...   print( 'TRUE' )
...   return True
...   
>>> def FALSE():
...   print( 'FALSE' )
...   return False
...

نعرِّف أولًا دالتين تخبراننا متى تُنفَّذان وتعيدان قيمة أسمائهما، ونستخدم ذلك لنرى كيفية تقييم التعابير البوليانية، لاحظ أن الخرج بالأحرف الكبيرة ناتج عن الدوال، والخرج بالأحرف مختلطة الحالة ناتج عن التعبير:

>>> print( TRUE() and FALSE() )
TRUE
FALSE
False
>>> print( TRUE() and TRUE() )
TRUE
TRUE
True
>>> print( FALSE() and TRUE() )
FALSE
False
>>> print( TRUE() or FALSE() )
TRUE
True
>>> print( FALSE() or TRUE() )
FALSE
TRUE
True
>>> print( FALSE() or FALSE() )
FALSE
FALSE
False

نلاحظ أنه إذا تحقق الجزء الأول من تعبير AND -أي كان True- وفقط إذا تحقق؛ فسيقيَّم الجزء الثاني، أما إذا لم يتحقق الجزء الأول -كان False- فلن يُقيَّم الجزء الثاني بما أن التعبير ككل لا يمكن أن يتحقق.

وبالمثل ففي التعبير المبني على OR، إذا كان الجزء الأول True فلا توجد حاجة إلى تقييم الجزء الثاني، لأن التعبير الكلي يجب أن يكون True، وذاك يتحقق بأحد الجزئين فقط.

هناك ميزة أخرى في تقييم بايثون للتعابير البوليانية يمكن استغلالها، وهي أنها لا تعيد -عند تقييم تعبير ما- True أو False فقط، بل تعيد القيمة الحقيقية للتعبير، لذا ستعيد بايثون السلسلة النصية نفسها إذا تحققنا من سلسلة فارغة -والتي يجب أن تُعد False- كما يلي:

if "This string is not empty": print( "Not Empty" )
else: print( "No string there" )

يمكن استخدام هذه الخصائص لإعادة إنتاج سلوك شبيه بالتفريع branching، لنفترض مثلًا أن لدينا جزءًا من شيفرة كما يلي:

if TRUE(): print( "It is True" )
else: print( "It is False" )

يمكن استبدال بنية وظيفية دالية بها:

V =  (TRUE() and "It is True") or ("It is False")
print( V )

جرب العمل على هذا المثال واستبدل استدعاءً إلى FALSE()‎ بالاستدعاء إلى TRUE()‎.

وهكذا نكون قد وجدنا طريقةً للتخلص من تعليمات if/else الشرطية في برامجنا باستخدام التقييم المقصور للتعابير البوليانية، وقد ترى هذه الأساليب في البرامج القديمة، لكنها قد تأتي بنتائج عكسية، لهذا توجد بنية أُدخلت حديثًا إلى بايثون تسمى بالتعبير الشرطي تسمح لنا بكتابة شرط if/else مثل تعبير، كما يلي:

result = <True expression> if <test condition> else <False expression>

وسيبدو مثال حقيقي بها كما يلي:

>>> print( "This is True" if TRUE() else "This is not printed" )
TRUE
This is True

وإذا استخدمنا else:

>>> print( "This is True" if FALSE() else "We see it this time" )
FALSE
We see it this time

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

ولنضع هذا في تدريب عملي سنكتب برنامج المضروب factorial بأسلوب وظيفية كليًا، باستخدام lambda بدلًا من def، والتعاودية بدلًا من الحلقات التكرارية، والتعابير الشرطية بدلًا من if/else المعتادة:

>>> factorial = lambda n: ( None if n < 0 else 
                            1 if (n == 0) else 
                            factorial(n-1) * n )
>>> print( factorial(5) )
120

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

استنتاجات

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

وعند الحاجة إلى تطبيق عمليات على عناصر في قائمة مثل map أو filter تكون البرمجية الوظيفية هي المثلى للتعبير عن الحل، وبالمثل قد نجد أحيانًا أن التعاودية أفضل من الحلقات التكرارية، كما قد نجد استخدامًا للتقييم المقصور أو التعبير الشرطي بدلًا من تعابير if/else الشرطية، خاصةً داخل تعبير ما، والمهم أنه يجب على المتعلم ألا يتحمس كثيرًا لتقنية أو فلسفة برمجية بعينها، وإنما يستخدم الأداة المناسبة للمهمة التي بين يديه، ويكفيه حينئذ أن يعلم بوجود حلول بديلة.

لدينا أمر أخير حول الدالة lambda، إذ يمكن استخدامها خارج مجال البرمجية الوظيفية، وهي تعريف معالجات الأحداث في برمجة الواجهات الرسومية، حيث تكون معالجات الأحداث دوالًا قصيرةً في الغالب، أو تستدعي دوالًا أكبر منها بقيم وسائط مدمجة فيها، وفي كلا الحالتين يمكن استخدام دالة لامدا مثل معالج حدث يتجنب الحاجة إلى تعريف الكثير من الدوال المنفردة وشغل فضاء الاسم بأسماء لن تُستخدم إلا مرةً واحدةً فقط، ويجب تذكر أن تعليمة لامدا تعيد كائن دالة يُمرَّر إلى الودجِت widget ويُستدعى في وقت وقوع الحدث، وقد عرَّفنا ودجت الزر في Tkinter من قبل، لذا ستبدو لامدا كما يلي:

b = Button(parent, text="Press Me", 
           command = lambda : print("I got pressed!"))
b.pack()

نلاحظ أنه رغم عدم السماح لمعالج الحدث بامتلاك معامِل إلا أننا نسمح بتحديد سلسلة داخل متن لامدا، ونستطيع الآن إضافة زر ثانٍ بسلسلة مختلفة كليًا:

b2 = Button(parent, text="Or Me", 
           command = lambda : print("I got pressed too!"))
b2.pack()

كما نستطيع توظيف lambda عند استخدام تقنية الربط bind technique التي ترسل كائن حدث مثل وسيط:

b3 = Button(parent, text="Press me as well")
b3.bind(<Button-1>, lambda ev : write("Pressed"))

البرمجية الوظيفية في جافاسكربت

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

<script type="text/javascript">
var i, values;

function times(m) {
    var results = new Array();
    for (i = 1; i <= 12; i++) {
        results[i] = i * m;
        }
    return results;
}


// استخدم الدالة
values = times(8);

for (i=1;i<=12;i++){
    document.write(values[i] + "<br />");
    }
</script>

لاحظ أننا عرّفنا الدالة بوضع الاسم times بعد الكلمة المفتاحية function، لكن جافاسكربت تسمح لنا بتسمية الدالة من خلال الإسناد، كما في أسلوب لامدا الخاص ببايثون أعلاه:

times = function(m) {
    var results = new Array();
    for (i = 1; i <= 12; i++) {
        results[i] = i * m;
        }
    return results;
}

لذا يكون times الآن متغيرًا يحمل مرجعًا إلى كائن الدالة، ويمكن استدعاؤه كما فعلنا من قبل:

values = times(8);

for (i=1;i<=12;i++){
    document.write(values[i] + "<br />");
    }

بل يمكن استخدام function()‎ لإنشاء دوال مجهولة كما في لامدا، باستثناء أنه ليس لجافاسكربت قيود على أنواع الدوال التي ننشئها، فيمكن أن تكون بأي طول وتعقيد نريده، مما يؤدي إلى نمط في برمجة جافاسكربت ستقابله غالبًا في صفحات الويب، ولذا سنسرد مثالًا مختصرًا له هنا، وهو يتضمن استخدام دوال جافاسكربت التي تأخذ دوالًا أخرى معامِلات لها، وقد رأينا هذا في برامج الواجهة الرسومية من قبل وسميناه رد النداء callback، لأن العديد من مكتبات جافاسكربت تستخدم رد النداء ذاك، ويكون عادةً من دالة رد نداء تُحدَّد على أنها المعامِل الأخير، وهنا مثال من رد نداء مُستخدَم في صفحة ويب بسيطة، وهي مثال كامل، لذا يمكن تحميله وتجربته في المتصفح:

<html>
<head>
<title>Callback test</title>
<script type="text/javascript">
window.setTimeout( function() {
       document.write("رأيتني الآن!");
     }, 3000);
</script>
</head>

<body>
<p>انتظر نهاية الوقت....<p>

</body>
</html>

نلاحظ أن الدالة التي نفذها الوقت المستقطع لي لها اسم، فقد أُنشئت وسيطًا أول في استدعاء setTimeout نفسه، ثم أضيف زمن الانتظار الذي هو 3000 مللي ثانية وسيطًا ثانيًا، فإذا حملته في المتصفح لديك فسترى وقت الانتظار يحل محل الرسالة الأولى بعد ثلاث ثوانٍ.

صار هذا الأسلوب من تعريف الدوال المضمَّنة شائعًا للغاية في مجتمع جافاسكربت، وما هو إلا برمجة وظيفية خالصة، وتحتوي JQuery وهي إحدى أشهر مكتبات الويب لجافاسكربت، على دوال كثيرة تأخذ دوالًا أخرى مثل معامِلات، لنحصل على أسلوب برمجي وظيفي للغاية.

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

خاتمة

نرجو في نهاية هذا المقال أن تكون تعلمت ما يلي:

  • البرامج الوظيفية ما هي إلا تعابير خالصة.
  • توفر بايثون map و filter و reduce و list comprehensions لدعم أسلوب البرمجية الوظيفية.
  • تعابير لامدا هي كتل من الشيفرات المجهولة التي لا تحمل اسمًا، ويمكن إسنادها إلى متغيرات أو استخدامها مثل دوال.
  • تقيِّم التعابير البوليانية عند الحاجة فقط لضمان النتيجة التي تمكن تلك التعابير من استخدامها مثل هياكل تحكم.
  • عند جمع مزايا البرمجية الوظيفية لبايثون مع التعاودية فمن الممكن كتابة أي دالة بأسلوب وظيفي في بايثون، رغم أننا لا ننصح بهذا.
  • تدعم جافاسكربت البرمجية الوظيفية، وهو الموجود الآن في أغلب صفحات الويب الحديثة.

ترجمة -بتصرف- للفصل الحادي والعشرين: Functional Programming من كتاب Learn To Program لصاحبه Alan Gauld.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...