سؤال

لقد قرأت مقالات ويكيبيديا لكليهما البرمجة الإجرائية و البرمجة الوظيفية, ، لكني مازلت في حيرة من أمري.هل يمكن لأحد أن يغليها حتى النخاع؟

هل كانت مفيدة؟

المحلول

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

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

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


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

نصائح أخرى

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

إجرائية:

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

بيرل 6

د 2

int factorial( int n ){

  int result = 1;

  for( ; n > 0 ; n-- ){
    result *= n;
  }

  return result;
}

وظيفي:

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

هاسكل

(منقول من ويكيبيديا );

fac :: Integer -> Integer

fac 0 = 1
fac n | n > 0 = n * fac (n-1)

أو في سطر واحد:

fac n = if n > 0 then n * fac (n-1) else 1

بيرل 6

د 2

pure int factorial( invariant int n ){
  if( n <= 1 ){
    return 1;
  }else{
    return n * factorial( n-1 );
  }
}

ملاحظة جانبية:

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

يوضح هذا المثال أيضًا إنشاء النطاق (2..$n) وعامل التشغيل الفوقي لتخفيض القائمة ([ OPERATOR ] LIST) مع عامل الضرب infix الرقمي.(*)
ويظهر أيضًا أنه يمكنك وضعه --> UInt في التوقيع بدلا من returns UInt بعد ذلك.

(يمكنك الابتعاد عن بدء النطاق بـ 2 كما سيعود "المشغل" المضاعف 1 عند الاتصال بدون أي وسائط)

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

وظيفي تركز البرمجة على التعبيرات

إجرائية تركز البرمجة على صياغات

التعبيرات لها قيم.البرنامج الوظيفي هو تعبير قيمته سلسلة من التعليمات التي يجب على الكمبيوتر تنفيذها.

لا تحتوي العبارات على قيم، وبدلاً من ذلك تقوم بتعديل حالة بعض الآلات المفاهيمية.

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

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

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

على سبيل المثال، قد تكون لغة C أكثر وظيفية من COBOL لأن استدعاء الدالة هو تعبير، في حين أن استدعاء برنامج فرعي في COBOL عبارة عن عبارة (تتعامل مع حالة المتغيرات المشتركة ولا تُرجع قيمة).ستكون Python أكثر وظيفية من لغة C لأنها تسمح لك بالتعبير عن المنطق الشرطي كتعبير باستخدام تقييم الدائرة القصيرة (test && path1 || path2 بدلاً من عبارات if).سيكون المخطط أكثر وظيفية من بايثون لأن كل شيء في المخطط عبارة عن تعبير.

لا يزال بإمكانك الكتابة بأسلوب وظيفي بلغة تشجع النموذج الإجرائي والعكس صحيح.من الصعب و/أو الأكثر صعوبة الكتابة في نموذج لا تشجعه اللغة.

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

أعتقد أن البرمجة الإجرائية/الوظيفية/الموضوعية تدور حول كيفية التعامل مع المشكلة.

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

كل نمط من أنماط البرمجة له ​​مميزاته ونقاط ضعفه.ومن ثم، فإن القيام بشيء مثل "البرمجة الخالصة" (أي.إجرائي بحت - لا أحد يفعل هذا، بالمناسبة، وهو أمر غريب نوعًا ما - أو وظيفي بحت أو موضوعي بحت) صعب جدًا، إن لم يكن مستحيلًا، باستثناء بعض المشكلات الأولية المصممة خصيصًا لإثبات ميزة أسلوب البرمجة (وبالتالي، نحن نسمي أولئك الذين يحبون النقاء "weenie" :D).

ومن ثم، من تلك الأنماط، لدينا لغات برمجة مصممة خصيصًا لتناسب بعض الأنماط.على سبيل المثال، التجميع يدور حول الإجراءات الإجرائية.حسنًا، معظم اللغات المبكرة إجرائية، وليس فقط Asm، مثل C وPascal (وFortran، كما سمعت).بعد ذلك، لدينا جميعًا Java الشهيرة في المدرسة الموضوعية (في الواقع، Java وC# موجودان أيضًا في فصل يسمى "الموجه نحو المال"، ولكن هذا موضوع لمناقشة أخرى).الهدف أيضًا هو Smalltalk.في المدرسة الوظيفية، سيكون لدينا عائلة "شبه وظيفية" (يعتبرها البعض غير نقية) عائلة Lisp وعائلة ML والعديد من عائلة Haskell، وErlang، "وظيفية بحتة"، وما إلى ذلك.وبالمناسبة، هناك العديد من اللغات العامة مثل Perl وPython وRuby.

للتوسع في تعليق كونراد:

ونتيجة لذلك، فإن البرنامج الوظيفي البحت ينتج دائمًا نفس القيمة للمدخلات، ولا يكون ترتيب التقييم محددًا جيدًا؛

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

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

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

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

fac n = foldr (*) 1 [1..n]

هو بناء اصطلاحي تمامًا، وأقرب بكثير من حيث الروح إلى استخدام الحلقة من استخدام التكرار الصريح.

البرمجة الوظيفية مماثلة للبرمجة الإجرائية التي توجد فيها المتغيرات العالمية لا تم استخدامها.

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

العودية هي مثال كلاسيكي لبرمجة النمط الوظيفي.

قال كونراد:

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

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

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

...من الصعب تصميم القيم غير المؤكدة مثل إدخال المستخدم أو القيم العشوائية بلغات وظيفية بحتة.

الحل لمشكلة الإدخال في البرامج الوظيفية البحتة هو تضمين لغة الأمر باعتبارها دي اس ال استخدام تجريد قوي بما فيه الكفاية.في اللغات الحتمية (أو غير الوظيفية) ليس هناك حاجة لذلك لأنه يمكنك "الغش" وتمرير الحالة ضمنيًا ويكون ترتيب التقييم واضحًا (سواء أعجبك ذلك أم لا).بسبب هذا "الغش" والتقييم القسري لجميع المعلمات لكل وظيفة، في اللغات الضرورية 1) تفقد القدرة على إنشاء آليات تدفق التحكم الخاصة بك (بدون وحدات الماكرو)، 2) التعليمات البرمجية ليست بطبيعتها آمنة و/أو قابلة للتوازي بشكل افتراضي, ، 3) وتنفيذ شيء مثل التراجع (السفر عبر الزمن) يتطلب عملاً دقيقًا (يجب على المبرمج الحتمي تخزين وصفة لاستعادة القيمة (القيم) القديمة!) لقد نسيت - "مجانًا".

آمل ألا يبدو هذا مثل التعصب، أردت فقط إضافة بعض المنظور.لا تزال البرمجة الحتمية وخاصة البرمجة النموذجية المختلطة بلغات قوية مثل C# 3.0 طرقًا فعالة تمامًا لإنجاز الأمور لا وجود للرصاص الفضي.

[1] ...باستثناء ما يتعلق باستخدام الذاكرة (راجع:Foldl وfoldl' في هاسكل).

للتوسع في تعليق كونراد:

وترتيب التقييم غير محدد جيدًا

تحتوي بعض اللغات الوظيفية على ما يسمى بالتقييم البطيء.مما يعني أنه لا يتم تنفيذ الوظيفة حتى تكون القيمة مطلوبة.حتى ذلك الوقت، تكون الوظيفة نفسها هي ما يتم تمريره.

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

إذا كانت لديك فرصة، فإنني أوصي بالحصول على نسخة من Lisp/Scheme، والقيام ببعض المشاريع فيه.معظم الأفكار التي أصبحت مؤخرًا عبارة عن عربات تم التعبير عنها في ليسب منذ عقود:البرمجة الوظيفية، والاستمرارية (كعمليات إغلاق)، وجمع البيانات المهملة، وحتى XML.

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

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

@ كريتون:

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

prouduct list = foldr 1 (*) list

أو ببساطة:

product = foldr 1 (*)

لذا فإن العامل "الاصطلاحي".

fac n = foldr 1 (*)  [1..n]

سيكون ببساطة

fac n = product [1..n]

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

num = 1 
def function_to_add_one(num):
    num += 1
    return num


function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)
function_to_add_one(num)

#Final Output: 2

البرمجة الإجرائية

num = 1 
def procedure_to_add_one():
    global num
    num += 1
    return num


procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()
procedure_to_add_one()

#Final Output: 6

function_to_add_one هي وظيفة

procedure_to_add_one هو إجراء

حتى لو قمت بتشغيل وظيفة خمس مرات، في كل مرة سيعود 2

إذا قمت بتشغيل إجراء خمس مرات، في نهاية الجولة الخامسة سوف يعطيك 6.

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

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

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

لفهم الفرق، يحتاج المرء إلى فهم أن نموذج "العراب" لكل من البرمجة الإجرائية والوظيفية هو النموذج البرمجة الحتمية.

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

الآن، فيما يتعلق بالبرمجة الوظيفية، فإن جوهر في منهجها هو أنها تحدد القيم التي يجب اتخاذها وكيف ينبغي نقل هذه القيم.(لذلك لا توجد حالة ولا توجد بيانات قابلة للتغيير لأنها تأخذ الوظائف كقيم من الدرجة الأولى وتمريرها كمعلمات إلى وظائف أخرى).

ملاحظة:إن فهم كل نموذج برمجة يتم استخدامه يجب أن يوضح الاختلافات بينهما جميعًا.

خدمات الدعم النفسي:في نهاية المطاف، نماذج البرمجة هي مجرد أساليب مختلفة لحل المشاكل.

خدمات الدعم النفسي: هذا إجابة quora لديها تفسير عظيم.

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

لنفترض أن لديك مصفوفة من السلاسل، وكل سلسلة تمثل عددًا صحيحًا مثل "5" أو "-200".تريد التحقق من مجموعة السلاسل المدخلة هذه مقابل حالة الاختبار الداخلية الخاصة بك (باستخدام مقارنة الأعداد الصحيحة).يتم عرض كلا الحلين أدناه

إجرائية

arr_equal(a : [Int], b : [Str]) -> Bool {
    if(a.len != b.len) {
        return false;
    }

    bool ret = true;
    for( int i = 0; i < a.len /* Optimized with && ret*/; i++ ) {
        int a_int = a[i];
        int b_int = parseInt(b[i]);
        ret &= a_int == b_int;  
    }
    return ret;
}

وظيفي

eq = i, j => i == j # This is usually a built-in
toInt = i => parseInt(i) # Of course, parseInt === toInt here, but this is for visualization

arr_equal(a : [Int], b : [Str]) -> Bool =
    zip(a, b.map(toInt)) # Combines into [Int, Int]
   .map(eq)
   .reduce(true, (i, j) => i && j) # Start with true, and continuously && it with each value

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

يتم تنفيذ ذلك عادةً باستخدام مكتبة خارجية مثل لوداش, ، أو متوفر مدمجًا بلغات أحدث مثل الصدأ.يتم تنفيذ الرفع الثقيل للبرمجة الوظيفية باستخدام وظائف/مفاهيم مثل map, filter, reduce, currying, partial, ، الثلاثة الأخيرة التي يمكنك البحث عنها لمزيد من الفهم.

إضافة

من أجل استخدامها في البرية، سيتعين على المترجم عادةً معرفة كيفية تحويل الإصدار الوظيفي إلى الإصدار الإجرائي داخليًا، نظرًا لأن الحمل الزائد لاستدعاء الوظيفة مرتفع جدًا.الحالات العودية مثل العامل الموضح ستستخدم حيلًا مثل مكالمة الذيل لإزالة استخدام الذاكرة O(n).حقيقة عدم وجود آثار جانبية تسمح للمترجمين الوظيفيين بتنفيذ && ret الأمثل حتى عندما .reduce تم أخيرًا.من الواضح أن استخدام Lodash في JS لا يسمح بأي تحسين، لذا فهو يمثل نجاحًا كبيرًا في الأداء (وهو ما لا يشكل عادةً مصدر قلق في تطوير الويب).سيتم تحسين لغات مثل Rust داخليًا (ولديها وظائف مثل try_fold لتساعد && ret تحسين).

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top