لماذا يقع المطورون في حب البرمجة الوظيفية؟

كانت البرمجة الوظيفية (FP) موجودة منذ 60 عامًا ، ولكن حتى الآن كان لها دائمًا نطاق ضيق إلى حد ما من الاستخدام. بينما تعتمد الشركات التي غيرت العالم مثل Google على المفاهيم الأساسية ، لا يعرف المبرمج الحديث المتوسط ​​سوى القليل جدًا ، إن وجد ، عن هذه الظاهرة.



لكن هذا سيتغير قريبا. يتم دمج المزيد والمزيد من مفاهيم FP في لغات مثل Java و Python . وهناك لغات أكثر حداثة مثل هاسكل تعمل بكامل طاقتها. لوضع البرمجة الوظيفية في مصطلحات بسيطة







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



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



البرمجة الوظيفية تدور حول القضاء على الآثار الجانبية



لفهم مبادئ البرمجة الوظيفية ، عليك أولاً أن تفهم ماهية "الوظيفة". قد يبدو الأمر مملًا ، لكنه في النهاية سيسمح لك برؤية ما هو غير مرئي للوهلة الأولى. لذلك دعونا نتحدث عن الوظائف.



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



def square(x):
    return x*x


هذه الوظيفة بسيطة للغاية. يأخذ حجة واحدة ، xربما تكون من النوع int، وربما من النوع floatأو double، وتعيد نتيجة xتربيع ذلك.



وهذه وظيفة أخرى:



global_list = []
def append_to_list(x):
    global_list.append(x)


للوهلة الأولى ، يبدو أنه يقبل xنوعًا ما ولا يُرجع شيئًا ، لأنه لا يوجد تعبير فيه return. لكن دعونا لا ننتقل إلى الاستنتاجات!



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



append_to_list(1)
append_to_list(2)
global_list


بعد استدعائين للوظيفة من المثال السابق ، global_listلن تكون هناك قائمة فارغة ، بل قائمة [1,2]. هذا يسمح لنا أن نقول أن القائمة ، في الواقع ، هي القيمة المقدمة لإدخال الوظيفة ، على الرغم من أن هذا لم يتم إصلاحه بأي شكل من الأشكال عند الإعلان عن الوظيفة. هذا يمكن أن يكون مشكلة.



عدم الأمانة عند إعلان الوظائف



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



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



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



newlist = []
def append_to_list2(x, some_list):
    some_list.append(x)
append_to_list2(1,newlist)
append_to_list2(2,newlist)
newlist


لم نغير الكثير في هذا الرمز. نتيجة لذلك ، تظهر الوظيفة في newlist، كما كان من قبل global_list، ويظهر [1,2]كل شيء آخر كما كان من قبل.



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



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



البرمجة الوظيفية هي كتابة وظائف خالصة



الوظيفة التي ، عند الإعلان عنها ، تشير بوضوح إلى ما تتطلبه وما ترجع إليه هي وظيفة ليس لها آثار جانبية. الوظيفة بدون آثار جانبية هي وظيفة خالصة.



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



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



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



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



▍ تخطيط وتقليل الوظائف



الحلقات هي آليات لا علاقة لها بالبرمجة الوظيفية. ألق نظرة على حلقات Python التالية:



integers = [1,2,3,4,5,6]
odd_ints = []
squared_odds = []
total = 0
for i in integers:
    if i%2 ==1
        odd_ints.append(i)
for i in odd_ints:
    squared_odds.append(i*i)
for i in squared_odds:
    total += i


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



والآن - نسخة أخرى من هذا الرمز:



from functools import reduce
integers = [1,2,3,4,5,6]
odd_ints = filter(lambda n: n % 2 == 1, integers)
squared_odds = map(lambda n: n * n, odd_ints)
total = reduce(lambda acc, n: acc + n, squared_odds)


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



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



▍ وظائف لامدا



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



وظائف Lambda هي أدوات يمكن استخدامها لكتابة البرامج بأسلوب وظيفي. ولكن يمكن استخدام هذه الوظائف في البرمجة الشيئية أيضًا.



▍ الكتابة الثابتة



المثال أعلاه غير مكتوب بشكل ثابت. لكنها مع ذلك عينة كود وظيفية.



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



وتجدر الإشارة إلى أن برمجة بعض اللغات أسهل من غيرها بأسلوب وظيفي.



بعض اللغات "وظيفية أكثر" من غيرها



▍ بيرل



لدى Perl نهج للتعامل مع الآثار الجانبية يميزها عن معظم اللغات الأخرى. أي أنه يحتوي على "متغير سحري" $_يجلب آثارًا جانبية إلى مستوى إحدى السمات الرئيسية للغة. تتمتع لغة Perl بمزاياها ، لكنني لن أحاول القيام ببرمجة وظيفية بهذه اللغة.



جافا



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



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



▍ سكالا



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



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



▍ بيثون



يتم تشجيع أسلوب البرمجة الوظيفية في بايثون. يمكن فهم ذلك إذا أخذنا في الاعتبار حقيقة أن كل وظيفة ، افتراضيًا ، لها معلمة واحدة على الأقل - self. هذا ، من نواح كثيرة ، من روح " Zen Python ": "الصريح أفضل من الضمني".



▍ كلوجور



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



▍ هاسكل



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



النتيجة



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



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



وهذا منطقي ، لأن البرمجة الوظيفية تظهر نفسها بشكل جيد في العمل مع قواعد البيانات ، في البرمجة المتوازية ، في مجال التعلم الآلي. وفي العقد الماضي ، كان كل هذا في ازدياد.



في حين أن التعليمات البرمجية الموجهة للكائنات لها فوائد لا حصر لها ، لا ينبغي التغاضي عن الكود الوظيفي. إذا تعلم المبرمج بعض المبادئ الأساسية لـ FP ، فقد يكون هذا ، في معظم الحالات ، كافيًا لتحسين مستواه المهني. ستساعده هذه المعرفة أيضًا على الاستعداد لـ "مستقبل وظيفي".



ما هو شعورك حيال البرمجة الوظيفية؟






All Articles