function - ما هو الفرق بين "الإغلاق" و "لامدا"؟




lambda functional-programming (7)

يمكن أن يفسر شخص ما؟ أفهم المفاهيم الأساسية وراءهم ، لكنني غالباً ما أراهم يستخدمون بالتبادل ويتم الخلط بيني.

والآن بعد أن أصبحنا هنا ، كيف يختلفان عن الوظيفة العادية؟


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

الإغلاق هو أي وظيفة تغلق فوق البيئة التي تم تعريفها فيها. هذا يعني أنه يمكن الوصول إلى المتغيرات ليس في قائمة المعلمات الخاصة به. أمثلة:

def func(): return h
def anotherfunc(h):
   return func()

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

def anotherfunc(h):
    def func(): return h
    return func()

لأنه هنا ، يتم تعريف func في anotherfunc ، وفي python 2.3 وأعظم (أو بعض الأرقام مثل هذا) عندما anotherfunc يحصلون على الإغلاق الصحيح (لا تزال الطفرة لا تعمل) ، وهذا يعني أنه يغلق فوق بيئة anotherfunc ويمكن الوصول إلى المتغيرات داخله. في Python 3.1+ ، تعمل الطفرة أيضًا عند استخدام الكلمة الرئيسية nonlocal .

نقطة أخرى مهمة - سوف تستمر شركة anotherfunc في الإغلاق على بيئة أخرى عندما لا يتم تقييمها في anotherfunc . سيعمل هذا الرمز أيضًا:

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

هذا سوف يطبع 10.

هذا ، كما تلاحظ ، لا علاقة له بـ lambda s - إنهما مفهومان مختلفان (على الرغم من ارتباطهما).


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


المفهوم هو نفسه كما هو موضح أعلاه ، ولكن إذا كنت من خلفية PHP ، فهذا يفسر أيضًا استخدام شفرة PHP.

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

وظيفة ($ v) {return $ v> 2؛ } هو تعريف دالة lambda. يمكننا حتى تخزينه في متغير ، بحيث يمكن إعادة استخدامه:

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

الآن ، ماذا لو كنت تريد تغيير الحد الأقصى المسموح به في المصفوفة التي تمت تصفيتها؟ يجب عليك كتابة وظيفة لامدا أخرى أو إنشاء إغلاق (PHP 5.3):

$max_comp = function ($max) {
  return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));

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

في ما يلي مثال أبسط على إغلاق PHP:

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

شرح جيد في هذا المقال.


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


ليست كل الإغلاقات هي lambdas وليس كل lambdas هي الإغلاق. كلاهما عبارة عن وظائف ، ولكن ليس بالضرورة بالطريقة التي اعتدنا معرفتها.

إن lambda هو في الأساس دالة يتم تحديدها في السطر بدلاً من الطريقة القياسية للإعلان عن الوظائف. Lambdas يمكن أن تنتقل في كثير من الأحيان ككائنات.

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

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

ما هو مثير للاهتمام هو أن إدخال Lambdas والإغلاق في C # يجلب برمجة وظيفية أقرب إلى الاستخدام السائد.


من وجهة نظر لغات البرمجة ، هما شيئان مختلفان تمامًا.

أساسا للغة كاملة تورينج نحن بحاجة فقط إلى عناصر محدودة للغاية ، مثل التجريد والتطبيق والحد. يوفر التجريد والتطبيق الطريقة التي يمكنك بها بناء تعبير lamdba ، والحد من dertermines معنى تعبير lambda.

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

(lambda (x y) (+ x y))

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

حسنًا ، الآن تخيل كيف يمكن تنفيذ ذلك. كلما قمنا بتطبيق تعبير لامدا على بعض التعبيرات ، على سبيل المثال

((lambda (x y) (+ x y)) 2 3)

يمكننا ببساطة استبدال المعلمات بالتعبير المراد تقييمه. هذا النموذج بالفعل قوي جدا. لكن هذا النموذج لا يُمكِّننا من تغيير قيم الرموز ، على سبيل المثال لا يمكننا تقليد تغيير الحالة. وبالتالي نحن بحاجة إلى نموذج أكثر تعقيدًا. لجعلها قصيرة ، كلما أردنا حساب معنى تعبير لامدا ، وضعنا زوج من الرمز والقيمة المقابلة في بيئة (أو جدول). ثم يتم تقييم الباقي (+ xy) عن طريق البحث عن الرموز المقابلة في الجدول. الآن إذا قدمنا ​​بعض البدائيات للعمل على البيئة مباشرة ، يمكننا أن نمذجة تغييرات الحالة!

مع هذه الخلفية ، تحقق من هذه الوظيفة:

(lambda (x y) (+ x y z))

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

((lambda (z) (lambda (x y) (+ x y z))) 1)

حتى تكون z ملزمة بـ 1 في جدول خارجي. لا يزال لدينا وظيفة تقبل معلمتين ولكن المعنى الحقيقي لها يعتمد أيضًا على البيئة الخارجية. بمعنى آخر ، تغلق البيئة الخارجية على المتغيرات الحرة. بمساعدة مجموعة !، يمكننا أن نجعل الوظيفة مهمة ، أي أنها ليست وظيفة بمعنى الرياضيات. ما يعود لا يعتمد فقط على المدخلات ، ولكن أيضا.

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

أستخدم مخطط لتوضيح الأفكار بسبب هذا المخطط هي واحدة من أقدم اللغات التي لديها إغلاق حقيقي. جميع المواد المعروضة هنا أفضل بكثير في الفصل 3 من برنامج SICP.

خلاصة القول ، لامدا والإغلاق هي مفاهيم مختلفة حقا. لامدا هي وظيفة. الإغلاق هو زوج من lambda والبيئة المقابلة التي تغلق lambda.


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

Lambda Calculus هي أبسط لغة برمجة في العالم. الأشياء الوحيدة التي يمكنك القيام بها:

  • التطبيق: تطبيق تعبير واحد لآخر ، دلالة fx .
    (فكر في الأمر كمكالمة للدالة ، حيث f هي الدالة و x هي المعلمة الوحيدة)
  • الخلاصة: يربط الرمز الذي يحدث في تعبير لوضع علامة على أن هذا الرمز مجرد "فتحة" ، مربع فارغ في انتظار ملؤه بالقيمة ، "متغير" كما كان. يتم ذلك عن طريق الإلحاق برسالة يونانية λ (lambda) ، ثم الاسم الرمزي (على سبيل المثال x ) ، ثم نقطة . قبل التعبير. هذا ثم تحويل التعبير إلى دالة تتوقع معلمة واحدة.
    على سبيل المثال: يأخذ λx.x+2 التعبير x+2 ويخبر أن الرمز x في هذا التعبير هو متغير منضم - يمكن استبداله بقيمة يتم توفيرها كمعلمة.
    لاحظ أن الوظيفة التي تم تعريفها بهذه الطريقة مجهولة - فهي لا تحتوي على اسم ، لذلك لا يمكنك الرجوع إليها بعد ، ولكن يمكنك الاتصال بها على الفور (تذكر التطبيق؟) من خلال توفيرها للمعلمة التي تنتظرها ، مثل هذا: (λx.x+2) 7 . ثم يتم استبدال التعبير (في هذه الحالة قيمة حرفية) 7 في x في subexpression x+2 من lambda المطبق ، بحيث تحصل على 7+2 ، والتي تنخفض بعد ذلك إلى 9 بواسطة قواعد حسابية شائعة.

لذلك قمنا بحل أحد الألغاز:
lambda هي الدالة المجهولة من المثال أعلاه ، λx.x+2 .

في لغات البرمجة المختلفة ، قد تختلف بنية التجريد الوظيفي (lambda). على سبيل المثال ، في JavaScript يبدو مثل:

function(x) { return x+2; }

ويمكنك تطبيقها على الفور على بعض المعلمات مثل هذا:

function(x) { return x+2; } (7)

أو يمكنك تخزين هذه الوظيفة المجهولة (lambda) في بعض المتغيرات:

var f = function(x) { return x+2; }

مما يمنحه اسمًا f ، مما يسمح لك بالرجوع إليه والاتصال به عدة مرات في وقت لاحق ، على سبيل المثال:

alert(  f(7) + f(10)  );   // should print 21 in the message box

لكن لم يكن عليك تسميتها. يمكنك الاتصال به على الفور:

alert(  function(x) { return x+2; } (7)  );  // should print 9 in the message box

في LISP ، يتم lambdas مثل هذا:

(lambda (x) (+ x 2))

ويمكنك استدعاء مثل lambda من خلال تطبيقه على الفور على معلمة:

(  (lambda (x) (+ x 2))  7  )

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

كما قلت ، ما يفعله تجريد لامدا هو رمز ملزمة في التعبير الفرعي الخاص به ، بحيث يصبح معلما بديلا. يسمى هذا الرمز مقيد . ولكن ماذا لو كانت هناك رموز أخرى في التعبير؟ على سبيل المثال: λx.x/y+2 . في هذا التعبير ، يرتبط الرمز x بـ λx. abstraction λx. يسبقها. لكن الرمز الآخر ، y ، غير ملزم - إنه مجاني . نحن لا نعرف ما هو وأين يأتي ، لذلك نحن لا نعرف ما يعنيه وما تمثله من قيمة ، وبالتالي لا يمكننا تقييم هذا التعبير حتى نعرف ما يعني y .

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

يمكنك أن تفكر في الرموز الحرة كما هي محددة في مكان آخر ، خارج التعبير ، في "سياقها المحيط" ، والذي يسمى بيئتها . قد تكون البيئة تعبيرًا أكبر أن هذا التعبير جزء من (كما قال Qui-Gon Jinn: "هناك دائمًا سمكة أكبر" ؛)) ، أو في بعض المكتبات ، أو في اللغة نفسها ( بدائية ).

هذا يتيح لنا تقسيم تعبيرات لامدا إلى فئتين:

  • تعبيرات مغلقة: كل رمز الذي يحدث في هذه التعبيرات ملزم ببعض تجريد لامدا. وبعبارة أخرى ، فهي قائمة بذاتها ؛ لا تتطلب أي سياق محيط ليتم تقييمه. وتسمى أيضا Combinators .
  • التعبيرات المفتوحة: بعض الرموز في هذه التعبيرات غير مرتبطة - أي أن بعض الرموز التي تحدث فيها خالية وهي تتطلب بعض المعلومات الخارجية ، وبالتالي لا يمكن تقييمها حتى تقدم تعريفات هذه الرموز.

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

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

على سبيل المثال ، إذا كان لديك تعبير lambda التالي: λx.x/y+2 ، فإن الرمز x مرتبط ، بينما يكون الرمز y حرًا ، لذلك يكون التعبير open ولا يمكن تقييمه إلا إذا قلت ما يعني y (و نفسه مع + و 2 ، والتي هي أيضا مجانية). لكن لنفترض أن لديك أيضًا بيئة كهذه:

{  y: 3,  +: [built-in addition],  2: [built-in number],  q: 42,  w: 5  }

توفر هذه البيئة تعريفات لكل الرموز "غير المحددة" (المجانية) من تعبيرنا lambda ( y ، + ، 2 )، والعديد من الرموز الإضافية ( q ، w ). الرموز التي نحتاج إلى تعريفها هي هذه المجموعة الفرعية من البيئة:

{  y: 3,  +: [built-in addition],  2: [built-in number]  }

وهذا هو بالضبط إغلاق تعبيرنا lambda:>

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

إذن لماذا هم مخطئون؟ لماذا يقول الكثيرون منهم أن الإغلاق هي بعض هياكل البيانات في الذاكرة ، أو بعض ميزات اللغات التي يستخدمونها ، أو لماذا يخلطون الإغلاق مع lambdas؟ : P

حسنًا ، إن اللوم يقع على سوقي الشركات لشركة Sun / Oracle ، و Microsoft ، و Google ، وما إلى ذلك ، لأن هذا ما أسموه هذه البنى بلغاتهم (Java و C # و Go etc.). وغالبا ما يطلقون على "الإغلاق" ما يفترض أن يكون مجرد دموع. أو يسمون "الإغلاق" تقنية معينة استخدموها لتنفيذ تحديد المدى المعجمي ، أي أن وظيفة يمكن الوصول إلى المتغيرات التي تم تعريفها في نطاقه الخارجي في وقت تعريفه. غالباً ما يقولون أن الدالة "encloses" هذه المتغيرات ، أي ، يلتقطها في بعض بنية البيانات لحفظها من يتم إتلاف بعد إنهاء الدالة الخارجية التنفيذ. ولكن هذا هو مجرد "علم الفولكلور" المؤلَّف بعد التسويق ، والذي يجعل الأمور أكثر إرباكًا ، لأن كل بائع لغة يستخدم مصطلحاته الخاصة.

بل والأسوأ من ذلك هو حقيقة أن هناك دائما القليل من الحقيقة في ما يقولونه ، والذي لا يسمح لك بسهولة رفضه كاذبة: P اسمحوا لي أن أشرح:

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

لم يستغرق الأمر وقتًا طويلاً حتى بدأ الناس في الاتصال بهيكل البيانات الفعلي الذي يستخدمونه في تطبيقات لغتهم لتطبيق الإغلاق كـ "الإغلاق" نفسه. عادة ما يبدو الهيكل كالتالي:

Closure {
   [pointer to the lambda function's machine code],
   [pointer to the lambda function's environment]
}

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

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

أي شيء آخر هو مجرد "عبادة البضائع" و "سحر voo-do" للمبرمجين وبائعي اللغات غير مدركين للجذور الحقيقية لهذه المفاهيم.

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





closures