มีอะไรพิเศษเกี่ยวกับการแกงหรือการใช้งานบางส่วน


9

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

ใช้โค้ด Groovy นี้เป็นตัวอย่าง:

def mul = { a, b -> a * b }
def tripler1 = mul.curry(3)
def tripler2 = { mul(3, it) }

ฉันไม่เข้าใจความแตกต่างระหว่างtripler1และtripler2คืออะไร พวกเขาทั้งคู่ไม่เหมือนกันหรือ 'การแกง' ได้รับการสนับสนุนในภาษาที่ใช้งานได้จริงหรือบางส่วนเช่น Groovy, Scala, Haskell เป็นต้น แต่ฉันสามารถทำสิ่งเดียวกันได้ (แกงเผ็ดซ้าย, แกงกะหรี่ขวา, แกงกะหรี่หรือแอปพลิเคชั่นบางส่วน) เพียงแค่สร้างชื่อหรือไม่ระบุชื่อ ฟังก์ชั่นหรือการปิดที่จะส่งต่อพารามิเตอร์ไปยังฟังก์ชั่นเดิม (เช่นtripler2) ในภาษาส่วนใหญ่ (แม้ C. )

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

โปรดให้ความกระจ่างแก่ฉัน

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


1
ใครก็ได้ช่วยสร้างแท็ก "แกงกะหรี่" หรือ "แกงกะหรี่"
Vigneshwaran

คุณจะแกงใน C อย่างไร
Giorgio

นี่น่าจะเป็นเรื่องของแอปพลิเคชั่นบางส่วนที่โปรแกรมเมอร์
. stackexchange.com/questions/152868/ ......

1
@Vigneshwaran: AFAIK คุณไม่จำเป็นต้องสร้างฟังก์ชั่นอื่นในภาษาที่รองรับการ Curry ตัวอย่างเช่นใน Haskell f x y = x + yหมายความว่าfเป็นฟังก์ชั่นที่รับพารามิเตอร์ int หนึ่งตัว ผลลัพธ์ของf x( fใช้กับx) เป็นฟังก์ชันที่รับพารามิเตอร์ int หนึ่งตัว ผลที่ตามมาf x y(หรือ(f x) yคือf xนำไปใช้y) x + yคือการแสดงออกที่จะป้อนพารามิเตอร์และไม่ได้รับการประเมินโดยการลด
Giorgio

1
คุณสามารถประสบความสำเร็จในสิ่งเดียวกันได้ แต่ความพยายามที่ผ่านไปกับ C นั้นเจ็บปวดและไม่ดีเท่าภาษาฮาสเคลล์ซึ่งมันเป็นพฤติกรรมเริ่มต้น
Daniel Gratzer

คำตอบ:


8

การดัดผมเป็นเรื่องเกี่ยวกับการเปลี่ยน / เป็นตัวแทนของฟังก์ชั่นที่ใช้ n อินพุตเป็นฟังก์ชั่น n ที่แต่ละใช้ 1 อินพุต แอปพลิเคชั่นบางส่วนเกี่ยวกับการแก้ไขอินพุตบางส่วนให้กับฟังก์ชัน

แรงจูงใจสำหรับบางส่วนของแอปพลิเคชั่นเป็นหลักที่ทำให้ง่ายต่อการเขียนไลบรารีฟังก์ชั่นการสั่งซื้อที่สูงขึ้น ตัวอย่างเช่นอัลกอริทึมในC ++ STLส่วนใหญ่ใช้ฟังก์ชันเพรดิเคตหรือยูนารีส่วนใหญ่bind1stอนุญาตให้ผู้ใช้ไลบรารีเชื่อมต่อในฟังก์ชันที่ไม่ใช่เอกภาพที่มีค่าถูกผูกไว้ ผู้เขียนไลบรารีจึงไม่จำเป็นต้องจัดเตรียมฟังก์ชั่นโอเวอร์โหลดสำหรับอัลกอริธึมทั้งหมดที่รับฟังก์ชั่นยูนารีเพื่อจัดเตรียมรุ่นไบนารี

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


เป็นcurryingสิ่งที่เฉพาะเจาะจงในการ groovy หรือใช้งานข้ามภาษา?
สัตว์สะเทินน้ำสะเทินบก

@foampile มันเป็นสิ่งที่ใช้ได้กับหลายภาษา แต่แกงกะหรี่ที่มีลักษณะเป็นเหล็กนั้นไม่ได้ทำหน้าที่เป็นผู้เขียนโปรแกรม
. stackexchange.com/questions/152868/ …

@jk คุณกำลังบอกว่าแอพพลิเคชั่นการแก้ปัญหา / บางส่วนมีประสิทธิภาพมากกว่าการสร้างและเรียกฟังก์ชั่นอื่นหรือไม่?
Vigneshwaran

2
@Vigneshwaran - มันไม่จำเป็นต้องมีประสิทธิภาพมากกว่า แต่มันมีประสิทธิภาพมากกว่าในแง่ของเวลาของโปรแกรมเมอร์ นอกจากนี้โปรดทราบว่าในขณะที่การแก้ปัญหาได้รับการสนับสนุนจากภาษาที่ใช้งานได้หลายภาษา แต่โดยทั่วไปจะไม่รองรับใน OO หรือภาษาเชิงปฏิบัติ (หรืออย่างน้อยก็ไม่ใช่ภาษา)
สตีเฟนซี

6

แต่ฉันสามารถทำสิ่งเดียวกัน (แอปพลิเคชั่นด้านซ้าย, แกงกะหรี่ขวา, n-curry หรือแอปพลิเคชั่นบางส่วน) เพียงแค่สร้างฟังก์ชันที่มีชื่อหรือไม่ระบุชื่อหรือปิดที่จะส่งต่อพารามิเตอร์ไปยังฟังก์ชันดั้งเดิม (เช่น tripler2) แม้ C. )

และเครื่องมือเพิ่มประสิทธิภาพจะพิจารณาสิ่งนั้นและไปยังสิ่งที่เข้าใจได้ทันที Currying เป็นเคล็ดลับเล็ก ๆ สำหรับผู้ใช้ แต่มีประโยชน์มากขึ้นจากมุมมองการออกแบบภาษา เป็นเรื่องที่ดีมากที่จะจัดการกับวิธีการต่าง ๆ อย่างที่เป็นเอกเทศA -> Bซึ่งBอาจเป็นวิธีอื่น

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


6

ในฐานะ @jk พาดพิงถึงการแกงสามารถช่วยทำให้รหัสทั่วไปมากขึ้น

ตัวอย่างเช่นสมมติว่าคุณมีสามฟังก์ชั่น (ใน Haskell):

> let q a b = (2 + a) * b

> let r g = g 3

> let f a b = b (a 1)

ฟังก์ชั่นfที่นี่ใช้สองฟังก์ชั่นเป็นอาร์กิวเมนต์ส่งผ่าน1ไปยังฟังก์ชันแรกและส่งผ่านผลลัพธ์ของการเรียกครั้งแรกไปยังฟังก์ชันที่สอง

ถ้าเราจะเรียกfใช้โดยใช้qและrเป็นข้อโต้แย้งมันจะทำอย่างมีประสิทธิภาพ:

> r (q 1)

ที่qจะนำไปใช้1และกลับฟังก์ชันอื่น (ตามที่qเป็นแกงกะหรี่); ฟังก์ชั่นนี้กลับมาจากนั้นก็จะถูกส่งผ่านไปเป็นอาร์กิวเมนต์ที่จะได้รับการโต้แย้งของr ผลจากการนี้จะเป็นค่าของ39

สมมติว่าเรามีฟังก์ชั่นอื่นอีกสองฟังก์ชั่น:

> let s a = 3 * a

> let t a = 4 + a

เราสามารถผ่านเหล่านี้ไปfได้เป็นอย่างดีและได้รับค่า7หรือ15ขึ้นอยู่กับว่าข้อโต้แย้งของเราได้หรือs t t sตั้งแต่ฟังก์ชันเหล่านี้ทั้งสองกลับค่ามากกว่าฟังก์ชั่นที่ไม่มีแอพลิเคชันบางส่วนจะเกิดขึ้นในหรือf s tf t s

ถ้าเราได้เขียนfด้วยqและrในใจเราอาจจะได้ใช้แลมบ์ดา (ฟังก์ชั่นที่ไม่ระบุชื่อ) แทนของโปรแกรมบางส่วนเช่น:

> let f' a b = b (\x -> a 1 x)

แต่นี้จะมีการ จำกัด f'การทั่วไปของ fสามารถเรียกได้ว่ามีข้อโต้แย้งqและrหรือsและtแต่f'สามารถถูกเรียกว่ามีqและr- f' s tและf' t sผลทั้งในข้อผิดพลาด

มากกว่า

หากf'ถูกเรียกด้วยq'/ r'คู่ที่q'ใช้เวลากว่าสองอาร์กิวเมนต์ที่จะยังคงจบลงด้วยการถูกนำมาใช้ในบางส่วนq'f'

อีกวิธีหนึ่งคุณสามารถห่อqด้านนอกfแทนที่จะเป็นข้างใน แต่นั่นจะทำให้คุณมีแลมบ์ซ้อนที่น่ารังเกียจ:

f (\x -> (\y -> q x y)) r

ซึ่งเป็นสิ่งสำคัญที่แกงกะหรี่qเป็นครั้งแรก!


คุณเปิดตาของฉัน คำตอบของคุณทำให้ฉันรู้ว่าฟังก์ชั่น curated / ใช้บางส่วนแตกต่างจากการสร้างฟังก์ชั่นใหม่ที่ส่งผ่านข้อโต้แย้งไปยังฟังก์ชั่นเดิม 1. การผ่านฟังก์ชั่น curryied / paed (เช่น f (q.curry (2)) นั้นดีกว่าการสร้างฟังก์ชั่นแยกต่างหากโดยไม่จำเป็นสำหรับการใช้งานชั่วคราว (ในภาษาที่ใช้งานได้เช่น groovy)
Vigneshwaran

2. ในคำถามของฉันฉันพูดว่า "ฉันสามารถทำแบบเดียวกันในค." ใช่ แต่ในภาษาที่ไม่สามารถใช้งานได้ซึ่งคุณไม่สามารถส่งผ่านฟังก์ชั่นเป็นข้อมูลได้สร้างฟังก์ชั่นแยกต่างหากซึ่งส่งต่อพารามิเตอร์ไปยังต้นฉบับไม่ได้ประโยชน์ทั้งหมดของการ currying / pa
Vigneshwaran

ฉันสังเกตเห็นว่า Groovy ไม่รองรับประเภทของการวางหลักเกณฑ์ทั่วไป Haskell รองรับ ผมต้องเขียนdef f = { a, b -> b a.curry(1) }เพื่อให้f q, rการทำงานและdef f = { a, b -> b a(1) }หรือdef f = { a, b -> b a.curry(1)() }สำหรับf s, tการทำงาน คุณต้องผ่านพารามิเตอร์ทั้งหมดหรือบอกว่าคุณกำลัง currying อย่างชัดเจน :(
Vigneshwaran

2
@Vigneshwaran: ใช่มันปลอดภัยที่จะบอกว่า Haskell และ currying ไปด้วยกันได้เป็นอย่างดี ;] หมายเหตุว่าใน Haskell, ฟังก์ชั่นแกงกะหรี่ (ในความหมายที่ถูกต้อง) โดยค่าเริ่มต้นและช่องว่างบ่งชี้การประยุกต์ใช้ฟังก์ชั่นเพื่อให้f x yหมายถึงสิ่งที่หลายภาษาจะเขียนไม่ได้f(x)(y) f(x, y)บางทีรหัสของคุณอาจทำงานใน Groovy ถ้าคุณเขียนqเพื่อคาดว่าจะเรียกว่าเป็นq(1)(2)อย่างไร
CA McCann

1
@Vigneshwaran ดีใจที่ฉันช่วยได้! ฉันรู้สึกเจ็บปวดที่ต้องบอกว่าคุณกำลังสมัครบางส่วน ใน Clojure ฉันต้องทำ(partial f a b ...)- คุ้นเคยกับ Haskell ฉันคิดถึงการแก้เผ็ดอย่างมากเมื่อเขียนโปรแกรมในภาษาอื่น (แม้ว่าฉันจะทำงานใน F # เมื่อเร็ว ๆ นี้ซึ่งขอบคุณการสนับสนุน)
พอล

3

มีสองประเด็นสำคัญเกี่ยวกับแอปพลิเคชั่นบางส่วน ข้อแรกคือวากยสัมพันธ์ / ความสะดวกสบาย - คำจำกัดความบางอย่างง่ายขึ้นและสั้นลงในการอ่านและเขียนตามที่ @ jk พูดถึง (ตรวจสอบการเขียนโปรแกรม Pointfreeสำหรับข้อมูลเพิ่มเติมเกี่ยวกับความยอดเยี่ยมนี้!)

ประการที่สองตามที่ @telastyn กล่าวถึงเป็นรูปแบบของฟังก์ชั่นและไม่เพียง แต่สะดวกเท่านั้น ในรุ่น Haskell ซึ่งฉันจะได้รับตัวอย่างของฉันเพราะฉันไม่คุ้นเคยกับภาษาอื่นที่มีแอพพลิเคชั่นบางส่วนฟังก์ชั่นทั้งหมดใช้อาร์กิวเมนต์เดียว ใช่ฟังก์ชั่นเช่น:

(:) :: a -> [a] -> [a]

รับอาร์กิวเมนต์เดียว เพราะความสัมพันธ์ของตัวสร้างประเภทฟังก์ชั่นดัง->กล่าวข้างต้นเทียบเท่ากับ:

(:) :: a -> ([a] -> [a])

ซึ่งเป็นฟังก์ชั่นที่ใช้เวลาอีกด้วยและผลตอบแทนการทำงานa[a] -> [a]

สิ่งนี้ทำให้เราสามารถเขียนฟังก์ชั่นเช่น:

($) :: (a -> b) -> a -> b

ซึ่งสามารถใช้ฟังก์ชั่นใด ๆกับข้อโต้แย้งของประเภทที่เหมาะสม แม้แต่คนที่คลั่งไคล้เช่น:

f :: (t, t1) -> t -> t1 -> (t2 -> t3 -> (t, t1)) -> t2 -> t3 -> [(t, t1)]
f q r s t u v = q : (r, s) : [t u v]

f' :: () -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)]
f' = f $ ((), 'a')  -- <== works fine

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

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

อย่างที่คุณเห็นชนิดนั้นคล้ายกันกับ$ถ้าคุณถอดApplicative fบิตออกไปและอันที่จริงคลาสนี้อธิบายแอปพลิเคชันฟังก์ชันในบริบท ดังนั้นแทนที่จะใช้ฟังก์ชั่นปกติ:

ghci> map (+3) [1..5]  
[4,5,6,7,8]

เราสามารถใช้ฟังก์ชั่นในบริบทการประยุกต์; ตัวอย่างเช่นในบริบทอาจมีบางสิ่งที่อาจมีอยู่หรือหายไป:

ghci> Just map <*> Just (+3) <*> Just [1..5]
Just [4,5,6,7,8]

ghci> Just map <*> Nothing <*> Just [1..5]
Nothing

ตอนนี้ส่วนที่เจ๋งจริงๆก็คือคลาสชนิดที่บังคับใช้ไม่ได้พูดถึงอะไรเกี่ยวกับฟังก์ชั่นของอาร์กิวเมนต์มากกว่าหนึ่งตัว - อย่างไรก็ตามมันสามารถจัดการกับมันได้แม้ฟังก์ชั่นของอาร์กิวเมนต์ 6 ข้อเช่นf:

fA' :: Maybe (() -> Char -> (t2 -> t3 -> ((), Char)) -> t2 -> t3 -> [((), Char)])
fA' = Just f <*> Just ((), 'a')

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


1
Applicativeโดยไม่ต้อง currying fzip :: (f a, f b) -> f (a, b)หรือโปรแกรมบางส่วนจะใช้ ในภาษาที่มีฟังก์ชั่นขั้นสูงนี้ช่วยให้คุณสามารถยกความดีความชอบและการประยุกต์ใช้บางส่วนในบริบท functor (<*>)และเทียบเท่ากับ หากไม่มีฟังก์ชั่นการสั่งซื้อที่สูงขึ้นคุณจะไม่มีfmapสิ่งทั้งปวงจะไร้ประโยชน์
CA McCann

@CAMcCann ขอบคุณสำหรับข้อเสนอแนะ! ฉันรู้ว่าฉันอยู่ในหัวของฉันด้วยคำตอบนี้ ดังนั้นสิ่งที่ฉันพูดผิดคืออะไร?

1
มันถูกต้องในจิตวิญญาณอย่างแน่นอน การแยกผมไปตามคำจำกัดความของ "รูปแบบทั่วไป", "เป็นไปได้" และการมี "ความคิดของบางส่วนของแอปพลิเคชัน" จะไม่เปลี่ยนความจริงง่ายๆที่f <$> x <*> yสไตล์ที่มีเสน่ห์ของสำนวนที่มีเสน่ห์ทำงานได้อย่างง่ายดาย กล่าวอีกนัยหนึ่งสิ่งที่น่าพอใจมีความสำคัญมากกว่าสิ่งที่เป็นไปได้ที่นี่
CA McCann

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

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