ความแตกต่างระหว่างการประยุกต์ใช้การแกงและบางส่วนคืออะไร


438

ฉันมักจะเห็นข้อร้องเรียนต่าง ๆ บนอินเทอร์เน็ตหลายครั้งว่าตัวอย่างของคนอื่นที่ไม่ใช่การแกง แต่เป็นเพียงบางส่วนเท่านั้น

ฉันไม่พบคำอธิบายที่ดีว่าแอปพลิเคชั่นบางส่วนคืออะไรหรือแตกต่างจากการแกง ดูเหมือนจะมีความสับสนโดยทั่วไปโดยมีตัวอย่างที่อธิบายได้ว่าเป็นการ currying ในบางสถานที่และการใช้งานบางส่วนในที่อื่น

ใครบางคนสามารถให้คำจำกัดความของคำทั้งสองและรายละเอียดว่าพวกเขาแตกต่างกันอย่างไร

คำตอบ:


256

ความดีความชอบคือการแปลงฟังก์ชั่นเดียวของnข้อโต้แย้งเข้าnฟังก์ชั่นที่มีอาร์กิวเมนต์เดียวในแต่ละ รับฟังก์ชั่นดังต่อไปนี้:

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 zภาษาการทำงานจำนวนมากให้คุณเขียน ถ้าคุณเพียงโทรf x yหรือ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

40
คุณกำลังบอกว่าแอพพลิเคชั่นบางส่วนเกิดขึ้นเมื่อคุณสั่งฟังก์ชั่นและใช้งานบางฟังก์ชั่น
SpoonMeiser

9
มากหรือน้อยใช่ หากคุณจัดหาชุดย่อยของอาร์กิวเมนต์คุณจะได้รับฟังก์ชันที่ยอมรับอาร์กิวเมนต์ที่เหลือ
Mark Cidade

1
การเปลี่ยนฟังก์ชั่น f (a, b, c, d) เป็น g (a, b) จะนับเป็นแอปพลิเคชั่นบางส่วนหรือไม่ หรือเป็นเฉพาะเมื่อใช้กับฟังก์ชั่น curried เท่านั้น ขออภัยที่ต้องเจ็บปวด แต่ฉันต้องการคำตอบที่ชัดเจนที่นี่
SpoonMeiser

2
@ Mark: ฉันคิดว่านี่เป็นเพียงหนึ่งในแนวคิดเหล่านั้นที่นำพาคนอวดรู้ในตัวฉัน - แต่การดึงดูดแหล่งข้อมูลที่เชื่อถือได้นั้นมีความน่าพอใจเพียงเล็กน้อย วิกิพีเดียเป็นสิ่งที่ฉันพิจารณาแหล่งข้อมูลที่เชื่อถือได้ยาก แต่ฉันเข้าใจว่ามันยากที่จะหาแหล่งอื่น พอเพียงที่จะบอกว่าฉันคิดว่าเราทั้งคู่ต่างก็รู้ว่าสิ่งที่เราพูดและอำนาจนั้นไม่ว่าเราจะเห็นด้วย (หรือไม่เห็นด้วย) ในรายละเอียดของพื้นถิ่น! :) ขอบคุณทำเครื่องหมาย!
เจสันตอม่อ

5
@ JasonBunting เกี่ยวกับความคิดเห็นแรกของคุณสิ่งที่คุณกำลังพูดถึงนั้นกำลังทวีความรุนแรง Currying กำลังใช้งานฟังก์ชั่น multi-arg เป็นอินพุตและส่งคืนเชนของฟังก์ชัน 1-arg เป็นเอาต์พุต De-currying กำลังใช้เชนของ 1-arg ฟังก์ชันเป็นอินพุตและส่งคืนฟังก์ชัน multi-arg เป็นเอาต์พุต ตามที่อธิบายไว้ในstackoverflow.com/a/23438430/632951
Pacerier

165

วิธีที่ง่ายที่สุดที่จะดูว่าพวกเขาแตกต่างคือการพิจารณาตัวอย่างจริง สมมติว่าเรามีฟังก์ชั่นAddที่ใช้เวลา 2 ตัวเลขเป็น input และส่งกลับตัวเลขเป็นผลผลิตเช่นผลตอบแทนAdd(7, 5) 12ในกรณีนี้:

  • การใช้ฟังก์ชันบางส่วนAddด้วยค่า7จะให้ฟังก์ชันใหม่เป็นเอาต์พุต ฟังก์ชั่นนั้นใช้ 1 หมายเลขเป็นอินพุตและเอาต์พุตตัวเลข เช่นนี้:

    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จะทำให้เรามีฟังก์ชั่นใหม่เป็นเอาท์พุท ฟังก์ชั่นที่ตัวเองใช้เวลา 1 จำนวนเป็นอินพุตและเอาต์พุตอีกฟังก์ชั่นใหม่ ฟังก์ชั่นที่สามนั้นจะรับ 1 หมายเลขเป็นอินพุตและส่งกลับตัวเลขเป็นเอาต์พุต เช่นนี้:

    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
    

กล่าวอีกนัยหนึ่ง "currying" และ "สมัครบางส่วน" เป็นสองหน้าที่ที่แตกต่างกันโดยสิ้นเชิง การ Currying ใช้ 1 อินพุตอย่างแน่นอนในขณะที่บางส่วนของแอปพลิเคชันรับอินพุต 2 (หรือมากกว่า)

แม้ว่าทั้งสองจะคืนค่าฟังก์ชันเป็นเอาต์พุต แต่ฟังก์ชันที่คืนค่านั้นมีรูปแบบที่แตกต่างกันโดยสิ้นเชิงดังที่แสดงไว้ด้านบน


24
แอพลิเคชันบางส่วนเปลี่ยนฟังก์ชั่นจากn-aryไป(x - n)-ary, ความดีความชอบจากการn-ary n * 1-aryฟังก์ชั่นที่ใช้บางส่วนมีขอบเขตที่ลดลง (ของโปรแกรม), ที่อยู่, คือการแสดงออกน้อยกว่าAdd7 Addในทางกลับกันฟังก์ชั่น curried จะแสดงออกเช่นเดียวกับฟังก์ชั่นเดิม
บ๊อบ

4
ฉันเชื่อว่าคุณลักษณะที่โดดเด่นมากขึ้นคือเมื่อเราแกงกะหรี่ f (x, y, z) => R, เราได้รับ f (x) ซึ่งให้ผลตอบแทน g (y) => h (z) => R แต่ละครั้งใช้อาร์กิวเมนต์เดียว; แต่เมื่อเราใช้ f (x, y, z) เป็น f (x) บางส่วนเราจะได้ g (y, z) => R นั่นคือด้วยสองข้อโต้แย้ง หากไม่ใช่สำหรับคุณลักษณะนั้นเราสามารถพูดได้ว่าการปิดบังนั้นเป็นเหมือนการใช้งานบางส่วนถึง 0 ข้อโต้แย้งดังนั้นจึงปล่อยข้อโต้แย้งทั้งหมดไว้ อย่างไรก็ตามในความเป็นจริง f () นำไปใช้กับอาร์กิวเมนต์ 0 บางส่วนเป็นฟังก์ชันที่บริโภค 3 args ในคราวเดียวซึ่งแตกต่างจาก curried f ()
Maksim Gumerov

2
อีกครั้งคำตอบที่ถูกต้องไม่ใช่คำตอบแรกหรือได้รับการโหวตมากที่สุดคำอธิบายง่ายๆของลายเซ็นของแกงกะหรี่กับบางส่วนที่ท้ายคำตอบนี้เป็นวิธีที่ง่ายที่สุดในการแก้ปัญหา
fnl

2
ความคิดเห็นf2(7)(5) is just a syntactic shortcutหมายความว่าอย่างไร (ฉันรู้น้อยมาก) ยังไม่มีf2/ "รู้เกี่ยวกับ" 7 อยู่แล้ว?
Zach Mierzejewski

@Pierier มีการcurryใช้งานที่ใดที่หนึ่ง (อย่าคิดว่ามันมีอยู่functools )
34499

51

หมายเหตุ: บทความนี้นำมาจากF # Basicsซึ่งเป็นบทความเบื้องต้นที่ยอดเยี่ยมสำหรับผู้พัฒนา. NET ที่เข้าสู่การเขียนโปรแกรมใช้งาน

การ Currying หมายถึงการทำลายฟังก์ชั่นที่มีข้อโต้แย้งมากมายในชุดของฟังก์ชั่นที่แต่ละข้อโต้แย้งหนึ่งและในที่สุดก็ก่อให้เกิดผลลัพธ์เช่นเดียวกับฟังก์ชั่นเดิม การ Currying อาจเป็นหัวข้อที่ท้าทายที่สุดสำหรับนักพัฒนามือใหม่ในการเขียนโปรแกรมเพื่อการใช้งาน คุณสามารถดูทั้งที่ทำงานในตัวอย่างนี้:

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

ทันทีคุณควรเห็นพฤติกรรมที่แตกต่างจากภาษาที่จำเป็นที่สุด คำสั่งที่สองสร้างฟังก์ชั่นใหม่ที่เรียกว่า double โดยส่งอาร์กิวเมนต์หนึ่งไปยังฟังก์ชันที่รับสองค่า ผลลัพธ์คือฟังก์ชั่นที่ยอมรับอาร์กิวเมนต์หนึ่งตัวและให้ผลลัพธ์เดียวกันกับที่คุณเรียกว่าคูณด้วย x เท่ากับ 2 และ y เท่ากับอาร์กิวเมนต์นั้น ในแง่ของพฤติกรรมมันเหมือนกับรหัสนี้:

let double2 z = multiply 2 z

บ่อยครั้งที่ผู้คนเข้าใจผิดว่าการทวีคูณนั้นถูกทำให้เป็นสองเท่า แต่นี่เป็นเรื่องจริงเท่านั้น ฟังก์ชั่นทวีคูณเป็น curried แต่ที่เกิดขึ้นเมื่อมันถูกกำหนดเพราะฟังก์ชั่นใน F # จะ curried โดยค่าเริ่มต้น เมื่อสร้างฟังก์ชั่นสองเท่าจะแม่นยำมากกว่าที่จะบอกว่ามีการใช้ฟังก์ชันทวีคูณบางส่วน

ฟังก์ชั่นทวีคูณเป็นชุดของสองฟังก์ชัน ฟังก์ชั่นแรกใช้เวลาหนึ่งอาร์กิวเมนต์ int และส่งกลับฟังก์ชั่นอื่นฟังก์ชั่นได้อย่างมีประสิทธิภาพผูกพัน x เพื่อค่าที่เฉพาะเจาะจง ฟังก์ชั่นนี้ยังยอมรับข้อโต้แย้ง int ที่คุณสามารถคิดว่าเป็นค่าที่ผูกกับ y หลังจากเรียกฟังก์ชั่นที่สองนี้ x และ y ถูกผูกไว้ทั้งคู่ดังนั้นผลลัพธ์คือผลผลิตของ x และ y ตามที่กำหนดไว้ในเนื้อหาของ double

ในการสร้างสองครั้งฟังก์ชันแรกในห่วงโซ่ของฟังก์ชันทวีคูณจะถูกประเมินเพื่อนำไปใช้คูณบางส่วน ฟังก์ชั่นที่เกิดขึ้นจะได้รับชื่อสองครั้ง เมื่อ double ถูกประเมินค่ามันจะใช้อาร์กิวเมนต์พร้อมกับค่าที่ใช้เพียงบางส่วนเพื่อสร้างผลลัพธ์


33

คำถามที่น่าสนใจ หลังจากการค้นหาเล็กน้อย"แอพพลิเคชั่นฟังก์ชั่นบางส่วนไม่ได้ปิด"ให้คำอธิบายที่ดีที่สุดที่ฉันพบ ฉันไม่สามารถพูดได้ว่าการปฏิบัติแตกต่างในนั้นเด่นชัดสำหรับฉัน แต่แล้วฉันไม่ใช่ผู้เชี่ยวชาญของ FP ...

อีกหน้าดูมีประโยชน์ (ซึ่งฉันยอมรับว่าฉันยังไม่ได้อ่านอย่างเต็มที่) คือ"แอปพลิเคชั่น Currying และบางส่วนกับ Java Closures"ปิด"

ดูเหมือนว่านี่จะเป็นคำศัพท์ที่สับสนอย่างมากในใจคุณ


5
ลิงค์แรกเป็นจุดที่เกี่ยวกับความแตกต่าง นี่คืออีกหนึ่งฉันพบว่ามีประโยชน์: bit.ly/CurryingVersusPartialApplication
Jason Bunting

5
การลองทำแบบนี้จะทำอย่างไรกับสิ่งอันดับ (การเปลี่ยนฟังก์ชั่นที่ใช้การโต้แย้งแบบ tuple เป็นสิ่งที่ใช้การแยกกันเป็น n และแยกกัน) แอปพลิเคชั่นบางส่วนคือความสามารถในการใช้ฟังก์ชั่นกับอาร์กิวเมนต์บางตัวซึ่งให้ฟังก์ชันใหม่สำหรับอาร์กิวเมนต์ที่เหลือ มันง่ายต่อการจดจำถ้าคุณแค่คิดว่าอยากแกง == เกี่ยวกับสิ่งอันดับ
ดอนสจ๊วต

9
@ ลิงก์จอนที่คุณโพสต์นั้นมีข้อมูล แต่จะเป็นการดีกว่าหากคุณขยายคำตอบและเพิ่มข้อมูลเพิ่มเติมที่นี่
Zaheer Ahmed


11
ไม่อยากจะเชื่อเลยว่าคุณมีลิงก์เพิ่มขึ้น 20 อันสำหรับการเชื่อมโยงคู่และการรับเข้าคุณไม่ทราบถึงความแตกต่างระหว่างแกงและแอปพลิเคชันบางส่วน เล่นได้ดีครับ
AlienWebguy

16

ฉันได้ตอบสิ่งนี้ในหัวข้ออื่นhttps://stackoverflow.com/a/12846865/1685865 https://stackoverflow.com/a/12846865/1685865ในระยะสั้นการใช้งานฟังก์ชั่นบางส่วนเป็นเรื่องเกี่ยวกับการแก้ไขข้อโต้แย้งของฟังก์ชั่นหลายตัวแปรที่ได้รับเพื่อให้ฟังก์ชั่นอื่นที่มีข้อโต้แย้งน้อยลงในขณะที่ Currying เป็นเรื่องเกี่ยวกับการเปลี่ยนฟังก์ชั่น การแกงจะปรากฏที่ส่วนท้ายของโพสต์นี้]

การหาความสนใจทางทฤษฎีเป็นส่วนใหญ่: เราสามารถแสดงการคำนวณโดยใช้ฟังก์ชั่นยูนารีเท่านั้น (นั่นคือทุกฟังก์ชั่นเป็นเอกภาพ) ในทางปฏิบัติและเป็นผลพลอยได้มันเป็นเทคนิคที่สามารถทำให้มีประโยชน์มากมาย (แต่ไม่ใช่ทั้งหมด) ส่วนการใช้งานเล็กน้อยหากภาษามีฟังก์ชั่น curried อีกครั้งมันไม่ได้หมายถึงเฉพาะการใช้งานบางส่วน ดังนั้นคุณสามารถเผชิญหน้ากับสถานการณ์ที่แอพพลิเคชั่นบางส่วนทำไปในทางอื่น แต่ผู้คนเข้าใจผิดว่า Currying

(ตัวอย่างการแกง)

ในทางปฏิบัติไม่เพียงแค่เขียน

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

เพื่อประโยชน์ในการ Currying


1
คุณจะบอกว่าการแกงเป็นกรณีเฉพาะของการสมัครบางส่วน?
SpoonMeiser

1
@SpoonMeiser, ไม่, การแกงไม่ใช่กรณีเฉพาะของแอปพลิเคชั่นบางส่วน: แอพพลิเคชั่นบางส่วนของฟังก์ชั่น 2-input นั้นไม่เหมือนกับการปิดฟังก์ชั่น ดูstackoverflow.com/a/23438430/632951
Pacerier

10

ความดีความชอบเป็นหน้าที่ของหนึ่งอาร์กิวเมนต์ซึ่งจะมีฟังก์ชั่นและผลตอบแทนฟังก์ชั่นใหม่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)

ทั้งสองฝ่ายจะให้ผลตอบแทนฟังก์ชั่นเดียวอาร์กิวเมนต์

ความเท่าเทียมกันไม่เป็นความจริงสำหรับฟังก์ชั่น arity ที่สูงกว่าเพราะในกรณีนี้การ currying จะส่งคืนฟังก์ชันหนึ่งอาร์กิวเมนต์ในขณะที่แอปพลิเคชันบางส่วนจะส่งคืนฟังก์ชันหลายอาร์กิวเมนต์

ความแตกต่างยังอยู่ในลักษณะการทำงานในขณะที่การเปลี่ยนแปลงการทำหน้าที่เดิมทั้งหมดซ้ำ (หนึ่งครั้งสำหรับแต่ละอาร์กิวเมนต์) การประยุกต์บางส่วนเป็นการเปลี่ยนขั้นตอนเดียว

ที่มา: วิกิพีเดียความดีความชอบ


8

ความแตกต่างระหว่างแกงและแอปพลิเคชั่นบางส่วนสามารถแสดงให้เห็นได้ดีที่สุดผ่านตัวอย่าง JavaScript ต่อไปนี้:

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

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

6 === partial(2, 3);

แอปพลิเคชันบางส่วนส่งผลให้ฟังก์ชันของ arity เล็กลง ในตัวอย่างข้างต้นfมี arity 3 ในขณะที่partialมีเพียง arity 2. ที่สำคัญฟังก์ชั่นที่ใช้บางส่วนจะส่งกลับผลลัพธ์ได้ทันทีเมื่อถูกเรียกใช้ไม่ได้ฟังก์ชั่นอื่นลงห่วงโซ่ currying ดังนั้นหากคุณเห็นบางสิ่งบางอย่างpartial(2)(3)มันไม่ใช่การใช้งานบางส่วนตามความเป็นจริง

อ่านเพิ่มเติม:


"ฟังก์ชั่นที่ใช้บางส่วนจะส่งคืนผลลัพธ์ทันทีที่ถูกเรียกใช้" - ไม่ถูกต้องใช่ไหม เมื่อฉันใช้ฟังก์ชันบางส่วนนิพจน์นั้นจะส่งคืนฟังก์ชันไม่ใช่ "ผลลัพธ์" ตกลงคุณอาจหมายถึงว่าฟังก์ชั่นหลังนี้เมื่อถูกเรียกด้วยอาร์กิวเมนต์ที่เหลืออยู่ส่งคืนผลลัพธ์ซึ่งแตกต่างจากการขุดหนึ่งก้าวลงไปในการแกง แต่ไม่มีใครบอกว่าคุณต้องระบุอาร์กิวเมนต์ที่เหลือทั้งหมด: คุณสามารถใช้ผลการสมัครบางส่วนได้บางส่วนและนั่นจะเป็นฟังก์ชั่นอีกครั้งไม่ใช่ "ผล"
Maksim Gumerov

6

คำตอบง่ายๆ

Curry:ช่วยให้คุณสามารถเรียกใช้ฟังก์ชันโดยแยกออกเป็นหลาย ๆ สายโดยมีหนึ่งอาร์กิวเมนต์ต่อการโทรหนึ่งครั้ง

บางส่วน:ช่วยให้คุณสามารถเรียกใช้ฟังก์ชั่นโดยแยกออกเป็นหลาย ๆ สายโดยมีอาร์กิวเมนต์หลายตัวต่อการโทร


คำแนะนำง่ายๆ

ทั้งสองช่วยให้คุณสามารถเรียกใช้ฟังก์ชันที่มีอาร์กิวเมนต์น้อยลง (หรือดีกว่าโดยให้เป็นฟังก์ชันแบบสะสม) ที่จริงแล้วทั้งคู่โยง (ในแต่ละการโทร) ค่าเฉพาะกับอาร์กิวเมนต์ที่ระบุของฟังก์ชัน

ความแตกต่างที่แท้จริงสามารถเห็นได้เมื่อฟังก์ชันมีอาร์กิวเมนต์มากกว่า 2 ข้อ


Simple e (c) (ตัวอย่าง)

(ใน Javascript)

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

ทำไมผ่านข้อโต้แย้งเช่นบริบทและการเรียกกลับถ้าพวกเขาจะเหมือนกันเสมอ? เพียงผูกค่าบางอย่างสำหรับฟังก์ชั่น

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

และเรียกมันว่าsubject1และ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');

คำปฏิเสธ

ฉันข้ามคำอธิบายทางวิชาการ / คณิตศาสตร์ทั้งหมด เพราะฉันไม่รู้ อาจช่วยได้🙃


4

ฉันมีคำถามนี้มากมายในขณะที่เรียนรู้และตั้งแต่นั้นมาก็ถูกถามหลายครั้ง วิธีที่ง่ายที่สุดที่ฉันสามารถอธิบายความแตกต่างได้ก็คือทั้งสองเหมือนกัน :) ให้ฉันอธิบาย ... มีความแตกต่างอย่างชัดเจน

ทั้งแอปพลิเคชั่นบางส่วนและการปิดบังเกี่ยวข้องกับการส่งอาร์กิวเมนต์ไปยังฟังก์ชันซึ่งอาจไม่พร้อมกันทั้งหมด ตัวอย่างที่ยอมรับอย่างเป็นธรรมคือการเพิ่มตัวเลขสอง ใน pseudocode (จริงๆแล้ว 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

กล่าวโดยย่อให้ใช้แอปพลิเคชั่นบางส่วนเพื่อเติมค่าบางค่าล่วงหน้าโดยรู้ว่าครั้งต่อไปที่คุณเรียกเมธอดมันจะดำเนินการโดยปล่อยอาร์กิวเมนต์ที่ไม่ได้รับการกำหนดไว้ทั้งหมด ใช้การแกงเมื่อคุณต้องการส่งคืนฟังก์ชันที่ใช้บางส่วนอย่างต่อเนื่องหลายครั้งเท่าที่จำเป็นเพื่อให้ลายเซ็นของฟังก์ชันนั้นสมบูรณ์ ตัวอย่างที่วางแผนไว้ขั้นสุดท้าย:

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

หวังว่านี่จะช่วยได้!

ปรับปรุง: บางภาษาหรือการใช้งาน lib จะช่วยให้คุณผ่าน arity (จำนวนข้อโต้แย้งทั้งหมดในการประเมินขั้นสุดท้าย) เพื่อการใช้งานบางส่วนซึ่งอาจทำให้คำอธิบายสองคำของฉันสับสนเป็นระเบียบ ... แต่ ณ จุดนั้นทั้งสองเทคนิค ส่วนใหญ่ใช้แทนกันได้


3

สำหรับฉันบางส่วนแอปพลิเคชันจะต้องสร้างฟังก์ชั่นใหม่ที่ข้อโต้แย้งที่ใช้จะถูกรวมเข้ากับฟังก์ชั่นที่เกิดขึ้นอย่างสมบูรณ์

ภาษาที่ใช้งานได้ส่วนใหญ่จะใช้การ currying โดยส่งคืนการปิด: อย่าประเมินภายใต้แลมบ์ดาเมื่อนำไปใช้เพียงบางส่วน ดังนั้นเพื่อให้แอพพลิเคชั่นบางส่วนมีความน่าสนใจเราจำเป็นต้องสร้างความแตกต่างระหว่างแอพพลิเคชั่นการแกงและแอปพลิเคชั่นบางส่วน


3

ฉันอาจจะผิดมากที่นี่เนื่องจากฉันไม่มีพื้นฐานที่แข็งแกร่งในวิชาคณิตศาสตร์หรือการเขียนโปรแกรมเชิงปฏิบัติการ แต่จากการโจมตีสั้น ๆ ของฉันไปสู่ ​​FP ดูเหมือนว่าการแก้ปัญหามีแนวโน้มที่จะเปลี่ยนฟังก์ชันของอาร์กิวเมนต์ N เป็นฟังก์ชัน N ของอาร์กิวเมนต์หนึ่งตัว ในขณะที่แอปพลิเคชันบางส่วน [ในทางปฏิบัติ] ทำงานได้ดีขึ้นด้วยฟังก์ชัน Variadic ที่มีจำนวนอาร์กิวเมนต์ไม่แน่นอน ฉันรู้ว่าบางตัวอย่างในคำตอบก่อนหน้านี้ท้าทายคำอธิบายนี้ แต่มันช่วยฉันได้มากที่สุดในการแยกแนวคิด ลองพิจารณาตัวอย่างนี้ (เขียนเป็น 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" และวางในรหัสของฉันเพื่อดูรุ่นที่คอมไพล์ซึ่งอาจหวังว่าจะสมเหตุสมผล ขอบคุณ!


2

ฉันจะสมมติว่าคนส่วนใหญ่ที่ถามคำถามนี้คุ้นเคยกับแนวคิดพื้นฐานอยู่แล้วดังนั้นพวกเขาไม่จำเป็นต้องพูดถึงเรื่องนี้ มันทับซ้อนกันซึ่งเป็นส่วนที่สับสน

คุณอาจจะสามารถใช้แนวคิดนี้ได้อย่างเต็มที่ แต่คุณเข้าใจพวกมันด้วยกันเพราะความไม่ชัดเจนของแนวคิด amorphous amorphous แบบเบลอ สิ่งที่ขาดหายไปคือการรู้ว่าเขตแดนระหว่างพวกเขาอยู่ที่ไหน

แทนที่จะกำหนดว่าแต่ละคนคืออะไรมันง่ายกว่าที่จะเน้นความแตกต่างของพวกเขา - ขอบเขต

การ Curryingคือเมื่อคุณกำหนดฟังก์ชั่น

แอปพลิเคชันบางส่วนคือเมื่อคุณเรียกใช้ฟังก์ชัน

แอปพลิเคชันคือคณิตศาสตร์ที่พูดสำหรับการเรียกใช้ฟังก์ชัน

แอปพลิเคชันบางส่วนต้องการเรียกใช้ฟังก์ชัน curried และรับฟังก์ชันเป็นชนิดส่งคืน


1

มีคำตอบที่ดีอื่น ๆ ที่นี่ แต่ฉันเชื่อว่าตัวอย่างนี้ (ตามความเข้าใจของฉัน) ใน 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 );
}

ดังนั้นการแกงจึงช่วยให้คุณมีฟังก์ชั่นหนึ่งอาร์กิวเมนต์เพื่อสร้างฟังก์ชั่นโดยที่บางส่วนของแอปพลิเคชันจะสร้างฟังก์ชั่น wrapper ที่โค้ดยาก ๆ อย่างน้อยหนึ่งอาร์กิวเมนต์

หากคุณต้องการคัดลอกและวางข้อมูลต่อไปนี้เป็นเสียงที่น่าฟัง แต่เป็นมิตรกับการทำงานเนื่องจากมีความผ่อนปรนมากกว่า:

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 );
}

ต่อไปนี้ให้ข้อมูลเชิงลึกที่สำคัญแก่ฉัน: "ดังนั้นการแก้ตัวทำให้คุณมีฟังก์ชันหนึ่งอาร์กิวเมนต์เพื่อสร้างฟังก์ชั่นโดยที่บางส่วนของแอปพลิเคชันจะสร้างฟังก์ชั่น wrapper
Roland

0

ในการเขียนสิ่งนี้ฉันสับสนและไม่แน่ใจ เป็นการแปลงผกผันในฟังก์ชัน ไม่สำคัญว่าคุณจะเรียกมันว่าอะไรตราบใดที่คุณได้รับการเปลี่ยนแปลงและการผกผันของมัน

การแยกจากกันไม่ได้กำหนดไว้ชัดเจนมาก (หรือค่อนข้างมีคำจำกัดความ "ขัดแย้งกัน" ที่ทุกคนจับวิญญาณของความคิด) โดยพื้นฐานแล้วมันหมายถึงการเปลี่ยนฟังก์ชั่นที่ใช้หลายอาร์กิวเมนต์เข้าไปในฟังก์ชั่นที่ใช้อาร์กิวเมนต์เดียว ตัวอย่างเช่น,

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

ทีนี้คุณจะเปลี่ยนสิ่งนี้เป็นฟังก์ชั่นที่รับอาร์กิวเมนต์เดี่ยวได้อย่างไร? คุณโกงแน่นอน!

plus :: (Int, Int) -> Int

โปรดสังเกตว่าบวกตอนนี้ใช้อาร์กิวเมนต์เดียว (ที่ประกอบด้วยสองสิ่ง) Super!

ประเด็นนี้คืออะไร? ถ้าคุณมีฟังก์ชั่นที่รับสองข้อโต้แย้งและคุณมีคู่ของข้อโต้แย้งมันเป็นเรื่องดีที่รู้ว่าคุณสามารถใช้ฟังก์ชั่นกับข้อโต้แย้งและยังได้รับสิ่งที่คุณคาดหวัง และที่จริงแล้วการวางท่อประปามีอยู่แล้วเพื่อให้คุณไม่ต้องทำสิ่งต่าง ๆ เช่นการจับคู่รูปแบบที่ชัดเจน สิ่งที่คุณต้องทำคือ:

(uncurry (+)) (1,2)

ดังนั้นการประยุกต์ใช้ฟังก์ชั่นบางส่วนคืออะไร? มันเป็นวิธีที่แตกต่างกันในการเปลี่ยนฟังก์ชั่นในสองข้อโต้แย้งเป็นฟังก์ชั่นที่มีหนึ่งอาร์กิวเมนต์ มันทำงานแตกต่างกัน อีกครั้งมาลอง (+) เป็นตัวอย่าง เราจะทำให้มันกลายเป็นฟังก์ชั่นที่ใช้ค่า Int เดียวเป็นอาร์กิวเมนต์ได้อย่างไร เราโกง!

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

นั่นคือฟังก์ชั่นที่เพิ่มศูนย์ให้กับ Int ใด ๆ

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

เพิ่ม 1 ให้กับ Int ใด ๆ ฯลฯ ในแต่ละกรณีเหล่านี้ (+) คือ "ใช้เพียงบางส่วน"

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.