Groovy เรียกแอปพลิเคชั่นบางส่วนว่า 'currying' หรือไม่?


15

Groovy มีแนวคิดที่เรียกว่า 'currying' นี่คือตัวอย่างจากวิกิ:

def divide = { a, b -> a / b }

def halver = divide.rcurry(2)

assert halver(8) == 4

ความเข้าใจของฉันเกี่ยวกับสิ่งที่เกิดขึ้นที่นี่ก็คือการโต้แย้งทางด้านขวาของdivideถูกผูกไว้กับค่า 2 ดูเหมือนว่ารูปแบบของการใช้งานบางส่วน

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

curry :: ((a, b) -> c) -> (a -> (b -> c))

สำหรับคนที่ไม่ได้ใช้ Haskell a, bและcพารามิเตอร์ทั่วไปทั้งหมด curryใช้เวลาฟังก์ชั่นที่มีสองข้อโต้แย้งและผลตอบแทนฟังก์ชั่นที่ใช้aและผลตอบแทนการทำงานจากการb cฉันได้เพิ่มวงเล็บพิเศษลงในประเภทเพื่อทำให้ชัดเจนยิ่งขึ้น

ฉันเข้าใจผิดหรือเปล่าว่าเกิดอะไรขึ้นในตัวอย่างของความแรงหรือเป็นเพียงบางส่วนที่ใช้ผิด? หรือทำทั้งสองอย่างในความเป็นจริง: กล่าวคือแปลงdivideเป็นฟังก์ชัน curried แล้วนำ2ไปใช้กับฟังก์ชันใหม่นี้บางส่วน


1
สำหรับผู้ที่ไม่พูด haskell msmvps.com/blogs/jon_skeet/archive/2012/01/30/…
jk

คำตอบ:


14

การนำไปใช้ของ Groovy curryไม่ได้เกิดขึ้นจริงในทุกช่วงเวลาแม้แต่หลังฉาก มันเป็นหลักเหมือนกับการประยุกต์ใช้บางส่วน

curry, rcurryและncurryวิธีการส่งคืนCurriedClosureวัตถุที่มีข้อโต้แย้งที่ถูกผูกไว้ นอกจากนี้ยังมีวิธีการgetUncurriedArguments(ฟังผิด - คุณแกงฟังก์ชั่นไม่ขัดแย้ง) ซึ่งส่งกลับองค์ประกอบของข้อโต้แย้งที่ส่งผ่านไปยังมันด้วยข้อโต้แย้งที่ถูกผูกไว้

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

if (objectClass == CurriedClosure.class) {
    // ...
    final Object[] curriedArguments = cc.getUncurriedArguments(arguments);
    // [Ed: Yes, you read that right, curried = uncurried. :) ]
    // ...
    return ownerMetaClass.invokeMethod(owner, methodName, curriedArguments);
}

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

ทางออกหนึ่งที่ฉันแนะนำคือการโอเวอร์โหลดcurryเมธอดดังกล่าวเมื่อไม่มีการส่งผ่านข้อโต้แย้งมันจะเป็นการแก้ปัญหาจริงและเลิกเรียกวิธีที่มีข้อโต้แย้งเพื่อสนับสนุนpartialฟังก์ชันใหม่ สิ่งนี้อาจดูแปลกไปเล็กน้อยแต่มันจะเพิ่มความเข้ากันได้ย้อนหลัง - เนื่องจากไม่มีเหตุผลที่จะใช้แอพพลิเคชั่นบางส่วนที่มีข้อโต้แย้งที่เป็นศูนย์ - ในขณะที่หลีกเลี่ยงสถานการณ์ที่ไม่ดี (IMHO) ชื่อcurryทำสิ่งที่แตกต่างและคล้ายกันอย่างสับสน

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

def add = { x, y -> x + y }
def addCurried = add.curry()   // should work like { x -> { y -> x + y } }
def add1 = addCurried(1)       // should work like { y -> 1 + y }
assert add1(1) == 2 

... และมันก็ใช้ได้เพราะaddCurriedควรทำเช่น{ x -> { y -> x + y } }นั้น แต่มันเป็นข้อยกเว้นรันไทม์และคุณตายภายในเล็กน้อย


1
ฉันคิดว่า rcurry และ ncurry ในฟังก์ชั่นที่มีอาร์กิวเมนต์> 2 แสดงให้เห็นว่านี่เป็นเพียงบางส่วนของแอปพลิเคชั่นที่ไม่ได้ปิด
jk

@jk ในความเป็นจริงมันสามารถพิสูจน์ได้ในฟังก์ชั่นที่มีข้อโต้แย้ง == 2 ตามที่ฉันทราบในตอนท้าย :)
Jordan Gray

3
@ matcauthon การพูดอย่างเคร่งครัด "วัตถุประสงค์" ของการปิดกั้นคือการแปลงฟังก์ชันที่มีอาร์กิวเมนต์จำนวนมากให้กลายเป็นฟังก์ชันที่ซ้อนกันโดยมีอาร์กิวเมนต์หนึ่งรายการ ฉันคิดว่าสิ่งที่คุณขอเป็นเหตุผลจริงที่คุณต้องการใช้การแกงซึ่งค่อนข้างยากที่จะพิสูจน์ว่าเป็น Groovy ได้ดีกว่า LISP หรือ Haskell ประเด็นคือสิ่งที่คุณอาจต้องการใช้เวลาส่วนใหญ่คือแอปพลิเคชั่นบางส่วนไม่ใช่การปิดบัง
Jordan Gray

4
+1 สำหรับand you die a little inside
Thomas Eding

1
ว้าวฉันเข้าใจการเรียนรู้ที่ดีขึ้นมากหลังจากอ่านคำตอบของคุณ: +1 และขอบคุณ! โดยเฉพาะอย่างยิ่งคำถามที่ฉันชอบที่คุณพูดว่า "conflated currying ด้วยการประยุกต์ใช้บางส่วน"
GlenPeterson

3

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

f :: (a,b,c) -> d

รูปแบบ curried ของมันจะเป็น

fcurried :: a -> b -> c -> d

แต่แกงกะหรี่ของ Groovy จะคืนสิ่งที่เทียบเท่า (สมมติว่ามี 1 อาร์กิวเมนต์ x)

fgroovy :: (b,c) -> d 

ซึ่งจะเรียก f ด้วยค่าคงที่ถึง x

เช่นในขณะที่แกงกะหรี่ของ Groovy สามารถส่งคืนฟังก์ชันด้วยอาร์กิวเมนต์ N-1 แต่ฟังก์ชัน curried อย่างถูกต้องมีอาร์กิวเมนต์ 1 เท่านั้นดังนั้น Groovy จึงไม่สามารถแกงด้วยแกง


2

Groovy ยืมการตั้งชื่อวิธีการแกงจากภาษา FP อื่น ๆ ที่ไม่บริสุทธิ์ซึ่งใช้การตั้งชื่อที่คล้ายกันสำหรับแอปพลิเคชันบางส่วน - อาจโชคร้ายสำหรับฟังก์ชันการทำงานที่เป็นศูนย์กลางของ FP มีการนำเสนอ currying "ของจริง" หลายอย่างเพื่อรวมไว้ใน Groovy กระทู้ที่ดีในการเริ่มอ่านเกี่ยวกับพวกเขาอยู่ที่นี่:

http://groovy.markmail.org/thread/c4ycxdzm3ack6xxb

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

สำหรับโปรแกรมเมอร์ OO ส่วนใหญ่ความแตกต่างระหว่างคำสองคำ (การสมัครใจและการสมัครบางส่วน) นั้นมีเนื้อหาเชิงวิชาการเป็นส่วนใหญ่ อย่างไรก็ตามเมื่อคุณคุ้นเคยกับพวกเขา (และใครก็ตามที่จะรักษารหัสของคุณได้รับการฝึกฝนให้อ่านสไตล์การเขียนโปรแกรมนี้) จากนั้นการเขียนโปรแกรมแบบไม่มีจุดหรือแบบโดยปริยาย และในบางกรณีอย่างหรูหรามากขึ้น เห็นได้ชัดว่ามี "ความงามอยู่ในสายตาของคนดู" ที่นี่ แต่การมีความสามารถในการรองรับสไตล์ทั้งสองนั้นสอดคล้องกับธรรมชาติของ Groovy (OO / FP, สเตติก / ไดนามิก, คลาส / สคริปต์ ฯลฯ )


1

รับข้อบกพร่องนี้พบได้ที่ IBM:

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

halverเป็นฟังก์ชั่นใหม่ (curried) ของคุณ (หรือปิด) ซึ่งตอนนี้ใช้เพียงพารามิเตอร์เดียว การโทรhalver(10)จะส่งผลให้ 5

ดังนั้นจึงแปลงฟังก์ชันที่มีอาร์กิวเมนต์ n ในฟังก์ชันที่มีอาร์กิวเมนต์ n-1 เช่นเดียวกันได้ถูกกล่าวโดยตัวอย่างของคุณ haskell สิ่งที่แกงทำ


4
คำจำกัดความของ IBM ไม่ถูกต้อง สิ่งที่พวกเขานิยามว่าการปิดตานั้นจริง ๆ แล้วเป็นแอปพลิเคชั่นฟังก์ชั่นบางส่วนซึ่งผูก (แก้ไข) ข้อโต้แย้งของฟังก์ชั่นที่จะทำให้ฟังก์ชั่นที่มี arity น้อยลง Currying แปลงฟังก์ชันที่รับอาร์กิวเมนต์จำนวนมากเป็นเชนของฟังก์ชันที่แต่ละอาร์กิวเมนต์ใช้หนึ่งอาร์กิวเมนต์
Jordan Gray

1
ในคำจำกัดความของ wikipedia นั้นเหมือนกับของ IBM: ในวิชาคณิตศาสตร์และวิทยาศาสตร์คอมพิวเตอร์ currying เป็นเทคนิคของการแปลงฟังก์ชันที่ใช้อาร์กิวเมนต์หลายตัว (หรือ n-tuple ของอาร์กิวเมนต์) ในลักษณะที่สามารถเรียกได้ว่าเป็น ฟังก์ชั่นโซ่ของแต่ละคนมีอาร์กิวเมนต์เดียว (แอพลิเคชันบางส่วน) Groovy ทำการแปลงฟังก์ชั่น (ที่มีสองอาร์กิวเมนต์) ด้วยrcurryฟังก์ชัน (ที่ใช้อาร์กิวเมนต์หนึ่งตัว) เป็นฟังก์ชัน (โดยมีเพียงอาร์กิวเมนต์เดียวเท่านั้น) ฉันถูกผูกมัดฟังก์ชันแกงด้วยอาร์กิวเมนต์ของฟังก์ชันพื้นฐานของฉันเพื่อรับฟังก์ชันผลลัพธ์ของฉัน
matcauthon

3
ไม่นิยามของวิกิพีเดียต่างกัน - แอพพลิเคชั่นบางส่วนคือเมื่อคุณเรียกใช้ฟังก์ชั่น curried - ไม่ใช่เมื่อคุณกำหนดมันซึ่งเป็นสิ่งที่ groovy ทำ
jk

6
@jk ถูกต้อง อ่านคำอธิบายของ Wikipedia อีกครั้งและคุณจะเห็นว่าสิ่งที่ได้รับคืนคือฟังก์ชันที่มีอาร์กิวเมนต์หนึ่งอันแต่ละอันไม่ใช่ฟังก์ชันที่มีn - 1อาร์กิวเมนต์ ดูตัวอย่างท้ายคำตอบของฉัน; ดูเพิ่มเติมในบทความสำหรับข้อมูลเพิ่มเติมเกี่ยวกับความแตกต่างที่เกิดขึ้น en.wikipedia.org/wiki/…
Jordan Gray

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