سؤال

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

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

هل يمكن لأحد أن يقدم لي تعريفًا لكلا المصطلحين وتفاصيل عن كيفية اختلافهما؟

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

المحلول

والضمادة هو تحويل وظيفة واحدة من ن الحجج إلى <م> ن وظائف مع حجة واحدة لكل منهما. وبالنظر إلى الدالة التالية:

function f(x,y,z) { z(x(y));}

عند بالكاري، يصبح:

function f(x) { lambda(y) { lambda(z) { z(x(y)); } } }

في أجل الحصول على التطبيق الكامل لو (س، ص، ض)، تحتاج إلى القيام بذلك:

f(x)(y)(z);

والعديد من اللغات وظيفية تتيح لك إرسال f x y z. إذا كان الاتصال فقط f x y أو <م> و (خ) (ذ) ثم تحصل على قيمة وظيفة إعادة تطبيقها جزئيا هو إغلاق lambda(z){z(x(y))} مع مرت في قيم x و y لf(x,y).

وطريقة واحدة لاستخدام التطبيق الجزئي هو تحديد وظائف التطبيقات الجزئية من الوظائف المعمم، مثل على أضعاف : ل

function fold(combineFunction, accumulator, list) {/* ... */}
function sum     = curry(fold)(lambda(accum,e){e+accum}))(0);
function length  = curry(fold)(lambda(accum,_){1+accum})(empty-list);
function reverse = curry(fold)(lambda(accum,e){concat(e,accum)})(empty-list);

/* ... */
@list = [1, 2, 3, 4]
sum(list) //returns 10
@f = fold(lambda(accum,e){e+accum}) //f = lambda(accumulator,list) {/*...*/}
f(0,list) //returns 10
@g = f(0) //same as sum
g(list)  //returns 10

نصائح أخرى

أسهل طريقة لمعرفة مدى اختلافها هي النظر في أ مثال حقيقي.لنفترض أن لدينا وظيفة Add الذي يأخذ رقمين كمدخلات ويعيد رقمًا كمخرجات، على سبيل المثال. Add(7, 5) عائدات 12.في هذه الحالة:

  • تطبيق جزئي الوظيفة Add مع قيمة 7 سيعطينا وظيفة جديدة كإخراج.تأخذ هذه الوظيفة نفسها رقمًا واحدًا كمدخل وتخرج رقمًا.كما:

    Partial(Add, 7); // returns a function f2 as output
    
                     // f2 takes 1 number as input and returns a number as output
    

    لذلك يمكننا القيام بذلك:

    f2 = Partial(Add, 7);
    f2(5); // returns 12;
           // f2(7)(5) is just a syntactic shortcut
    
  • الكاري الوظيفة Add سيعطينا وظيفة جديدة كإخراج.تأخذ هذه الوظيفة نفسها رقمًا واحدًا كمدخلات ومخرجات حتى الآن وظيفة جديدة أخرى.تأخذ هذه الدالة الثالثة رقمًا واحدًا كمدخل وترجع رقمًا كمخرج.كما:

    Curry(Add); // returns a function f2 as output
    
                // f2 takes 1 number as input and returns a function f3 as output
                // i.e. f2(number) = f3
    
                // f3 takes 1 number as input and returns a number as output
                // i.e. f3(number) = number
    

    لذلك يمكننا القيام بذلك:

    f2 = Curry(Add);
    f3 = f2(7);
    f3(5); // returns 12
    

بمعنى آخر، "الكاري" و"التطبيق الجزئي" هما وظيفتان مختلفتان تمامًا. يتطلب الكاري مدخلاً واحدًا بالضبط، بينما يتطلب التطبيق الجزئي مدخلين (أو أكثر).

على الرغم من أن كلاهما يعيدان دالة كمخرجات، إلا أن الدوال التي يتم إرجاعها لها أشكال مختلفة تمامًا كما هو موضح أعلاه.

ملحوظة: تم أخذ هذا من F # أساسيات مقالة افتتاحية ممتازة للمطورين. NET الحصول على في البرمجة الوظيفية.

<اقتباس فقرة>   

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

let multiply x y = x * y    
let double = multiply 2
let ten = double 5
     

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

let double2 z = multiply 2 z
     

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

     

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

     

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

والسؤال للاهتمام. بعد قليل من البحث، "الجزئي وظيفة التطبيق لا الضمادة" أعطى أفضل تفسير I وجدت. لا أستطيع أن أقول أن <م> عملي الفرق واضح بشكل خاص بالنسبة لي، ولكن بعد ذلك أنا لست خبيرا FP ...

الصفحة أبحث مفيدة-آخر (وأنا أعترف بأنني لم أقرأ بشكل كامل بعد) هو <وأ href = "http://markmahieu.blogspot.com/2007/12/currying-and-partial-application-with. أتش تي أم أل "يختلط =" noreferrer ">" الضمادة والتطبيق الجزئي مع جافا الإغلاق ".

ويبدو بالفعل ان هذا هو الزوج على نطاق واسع الخلط بين المصطلحات، فتذكروا.

ولقد أجبت على هذا في موضوع https://stackoverflow.com/a/12846865/1685865 . باختصار، تطبيق وظيفة الجزئي عن تحديد بعض حجج وظيفة متعددة المتغيرات نظرا لتسفر عن وظيفة أخرى مع عدد أقل من الحجج، بينما الضمادة عبارة عن تحول وظيفة من الحجج N إلى وظيفة الأحادية التي تقوم بإرجاع وظيفة الأحادية ... [مثال ومبين الضمادة في نهاية هذا المنصب.]

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

(مثال الضمادة)

في ممارسة واحدة لن مجرد كتابة

lambda x: lambda y: lambda z: x + y + z

وأو جافا سكريبت يعادل

function (x) { return function (y){ return function (z){ return x + y + z }}}

وبدلا من

lambda x, y, z: x + y + z

ومن أجل الضمادة.

الكاري هو وظيفة واحد الوسيطة التي تأخذ وظيفة f وإرجاع وظيفة جديدة h.لاحظ أن h يأخذ حجة من X ويعود أ وظيفة أن الخرائط Y ل Z:

curry(f) = h 
f: (X x Y) -> Z 
h: X -> (Y -> Z)

التطبيق الجزئي هو وظيفة اثنان (أو أكثر) الحجج التي تأخذ وظيفة f وواحدة أو أكثر من الحجج الإضافية ل f وإرجاع وظيفة جديدة g:

part(f, 2) = g
f: (X x Y) -> Z 
g: Y -> Z

ينشأ الالتباس لأنه مع دالة ذات وسيطتين، فإن المساواة التالية تحمل:

partial(f, a) = curry(f)(a)

سوف ينتج عن كلا الجانبين نفس وظيفة الوسيطة الواحدة.

لا تكون المساواة صحيحة بالنسبة للدوال ذات الدقة العالية لأنه في هذه الحالة، سيُرجع الكاري دالة ذات وسيطة واحدة، بينما سيُرجع التطبيق الجزئي دالة متعددة الوسائط.

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

مصدر: ويكيبيديا الكاري.

يمكن توضيح الفرق بين الكاري والتطبيق الجزئي بشكل أفضل من خلال مثال JavaScript التالي:

function f(x, y, z) {
    return x + y + z;
}

var partial = f.bind(null, 1);

6 === partial(2, 3);

وينتج عن التطبيق الجزئي دالة أصغر؛في المثال أعلاه، f لديه arity من 3 بينما partial لديه فقط arity 2.والأهم من ذلك، أن الوظيفة المطبقة جزئيًا ستكون كذلك إرجاع النتيجة فورًا عند استدعائها, وليست وظيفة أخرى في سلسلة الكاري.لذلك إذا كنت ترى شيئا من هذا القبيل partial(2)(3), ، فهو ليس تطبيقًا جزئيًا في الواقع.

قراءة متعمقة:

وبالنسبة لي التطبيق الجزئي يجب إنشاء وظيفة جديدة حيث يتم دمج الحجج المستخدمة تماما في وظيفة الناتجة عن ذلك.

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

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

# partial application
partial_apply = (func) ->
  args = [].slice.call arguments, 1
  -> func.apply null, args.concat [].slice.call arguments

sum_variadic = -> [].reduce.call arguments, (acc, num) -> acc + num

add_to_7_and_5 = partial_apply sum_variadic, 7, 5

add_to_7_and_5 10 # returns 22
add_to_7_and_5 10, 11, 12 # returns 45

# currying
curry = (func) ->
  num_args = func.length
  helper = (prev) ->
    ->
      args = prev.concat [].slice.call arguments
      return if args.length < num_args then helper args else func.apply null, args
  helper []

sum_of_three = (x, y, z) -> x + y + z
curried_sum_of_three = curry sum_of_three
curried_sum_of_three 4 # returns a function expecting more arguments
curried_sum_of_three(4)(5) # still returns a function expecting more arguments
curried_sum_of_three(4)(5)(6) # returns 15
curried_sum_of_three 4, 5, 6 # returns 15

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

مرة أخرى، هذا هو رأيي من الأشياء التي قرأتها.إذا كان أي شخص لا يوافق، سأكون ممتنًا للتعليق على السبب بدلاً من التصويت السلبي الفوري.أيضًا، إذا كان من الصعب قراءة CoffeeScript، يرجى زيارة موقع Coffeescript.org، ثم انقر فوق "تجربة Coffeescript" ولصق الكود الخاص بي لرؤية النسخة المجمعة، والتي (نأمل) قد تكون أكثر منطقية.شكرًا!

وكان لي هذا السؤال كثيرا حين تعلم ومنذ ذلك الحين طلبت ذلك مرات عديدة. إن أبسط وسيلة يمكنني وصف الفرق هو أن كلا هي نفسها :) اسمحوا لي أن أشرح ... هناك بالطبع اختلافات.

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

add = (x, y) => x + y

إذا أردت وظيفة "addOne"، ويمكنني أن تطبيقه جزئيا أو الكاري ما يلي:

addOneC = curry(add, 1)
addOneP = partial(add, 1)

والآن باستخدام منهم هو واضح:

addOneC(2) #=> 3
addOneP(2) #=> 3

وإذن ما هو الفرق؟ حسنا، انها خفية، ولكنه يتضمن التطبيق الجزئي توريد بعض الحجج وظيفة عاد بعد ذلك سوف <م> تنفيذ المهمة الرئيسية على الاحتجاج المقبل في حين التمشيط سوف يبقى الانتظار حتى أنه لديه كل الحجج الضرورية:

curriedAdd = curry(add) # notice, no args are provided
addOne = curriedAdd(1) # returns a function that can be used to provide the last argument
addOne(2) #=> returns 3, as we want

partialAdd = partial(add) # no args provided, but this still returns a function
addOne = partialAdd(1) # oops! can only use a partially applied function once, so now we're trying to add one to an undefined value (no second argument), and we get an error

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

curriedAdd = curry(add)
curriedAdd()()()()()(1)(2) # ugly and dumb, but it works

partialAdd = partial(add)
partialAdd()()()()()(1)(2) # second invocation of those 7 calls fires it off with undefined parameters

وآمل أن يساعد هذا!

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

إجابة بسيطة

كاري: يتيح لك استدعاء دالة، وتقسيمها إلى مكالمات متعددة، وتوفير وسيطة واحدة لكل مكالمة.

جزئي: يتيح لك استدعاء وظيفة، وتقسيمها إلى مكالمات متعددة، وتوفير وسائط متعددة لكل مكالمة.


تلميحات بسيطة

كلاهما يسمح لك باستدعاء دالة توفر وسيطات أقل (أو الأفضل، توفيرها بشكل تراكمي).في الواقع، كلاهما يربط (عند كل مكالمة) قيمة محددة لوسائط محددة للوظيفة.

يمكن رؤية الفرق الحقيقي عندما تحتوي الدالة على أكثر من وسيطتين.


بسيط ه(ج)(عينة)

(في جافا سكريبت)

function process(context, success_callback, error_callback, subject) {...}

لماذا يتم تمرير الوسائط دائمًا، مثل السياق وعمليات الاسترجاعات، إذا كانت هي نفسها دائمًا؟فقط قم بربط بعض القيم للوظيفة

processSubject = _.partial(process, my_context, my_success, my_error)

والاتصال به الموضوع1 و com.foobar مع

processSubject('subject1');
processSubject('foobar');

مريح، أليس كذلك؟😉

مع الكاري ستحتاج إلى تمرير وسيطة واحدة في كل مرة

curriedProcess = _.curry(process);
processWithBoundedContext = curriedProcess(my_context);
processWithCallbacks = processWithBoundedContext(my_success)(my_error); // note: these are two sequential calls

result1 = processWithCallbacks('subject1');
// same as: process(my_context, my_success, my_error, 'subject1');
result2 = processWithCallbacks('foobar'); 
// same as: process(my_context, my_success, my_error, 'foobar');

تنصل

لقد تخطيت كل التفسير الأكاديمي/الرياضي.لأنني لا أعرف ذلك.ربما ساعد 🙃

هناك إجابات رائعة أخرى هنا ولكني أعتقد أن هذا المثال (حسب فهمي) في Java قد يكون مفيدًا لبعض الأشخاص:

public static <A,B,X> Function< B, X > partiallyApply( BiFunction< A, B, X > aBiFunction, A aValue ){
    return b -> aBiFunction.apply( aValue, b );
}

public static <A,X> Supplier< X > partiallyApply( Function< A, X > aFunction, A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  A, Function< B, X >  > curry( BiFunction< A, B, X > bif ){
    return a -> partiallyApply( bif, a );
}

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

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

public static <A,B,X> Function< ? super B, ? extends X > partiallyApply( final BiFunction< ? super A, ? super B, X > aBiFunction, final A aValue ){
    return b -> aBiFunction.apply( aValue, b );
}

public static <A,X> Supplier< ? extends X > partiallyApply( final Function< ? super A, X > aFunction, final A aValue ){
    return () -> aFunction.apply( aValue );
}

public static <A,B,X> Function<  ? super A,  Function< ? super B, ? extends X >  > curry( final BiFunction< ? super A, ? super B, ? extends X > bif ){
    return a -> partiallyApply( bif, a );
}

في كتابه هذا، وأنا الخلط التمشيط وuncurrying. وهي تحولات عكسية على وظائف. انها حقا لا يهم ما تسمونه التي طالما تحصل على ما التحول وعكسه تمثل.

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

(+) :: Int -> Int -> Int

والآن، كيف يمكن تحويل ذلك إلى وظيفة أن يأخذ حجة واحدة؟ يمكنك خداع، وبطبيعة الحال!

plus :: (Int, Int) -> Int

لاحظ أن بالإضافة إلى الآن يأخذ حجة واحدة (التي تتألف من اثنين من الأشياء). سوبر!

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

(uncurry (+)) (1,2)

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

((+) 0) :: Int -> Int

وهذا هو وظيفة يضيف صفر إلى أي كثافة العمليات.

((+) 1) :: Int -> Int

ويضيف 1 إلى أي كثافة العمليات. الخ وفي كل من هذه الحالات، (+) هو "تطبيق جزئيا".

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

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

بدلاً من تحديد ماهية كل واحدة منها، من الأسهل تسليط الضوء على الاختلافات بينها فقط - الحدود.

الكاري هو عندما كنت يُعرِّف الوظيفة.

التطبيق الجزئي هو عندما كنت يتصل الوظيفة.

طلب هو الحديث عن الرياضيات لاستدعاء وظيفة.

جزئي يتطلب التطبيق استدعاء دالة متجانسة والحصول على دالة كنوع الإرجاع.

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