'Currying' คืออะไร


652

ฉันเคยเห็นการอ้างอิงถึงฟังก์ชั่น curated ในหลายบทความและบล็อก แต่ฉันไม่สามารถหาคำอธิบายที่ดี (หรืออย่างน้อยหนึ่งที่ทำให้รู้สึก!)


12
[ทิ้งไว้เป็นความคิดเห็นเนื่องจากมันจะไร้ประโยชน์กับผู้ที่ไม่ใช่นักคณิตศาสตร์] ตามคำจำกัดความของหมวดหมู่ที่ปิดคาร์ทีเซียนมีครอบครัวที่คงที่ของคำสันธาน (โดยธรรมชาติ parametrized โดย A) ระหว่าง X -> X x A และ X -> X ^ A. isomorphisms hom (X x A, Y) <-> hom (X, Y ^ A) คือcurryและuncurryหน้าที่ของ Haskell สิ่งสำคัญคือที่นี่ isomorphisms เหล่านี้ได้รับการแก้ไขล่วงหน้าและดังนั้น "ในตัว" เป็นภาษา
Alexandre C.

3
มีแบบฝึกหัดที่ดีที่นี่สำหรับการเรียนการสอนใน haskell learnyouahaskell.com/higher-order-functions#curried-functionsความคิดเห็นสั้น ๆ คือadd x y = x+y(curried) นั้นแตกต่างจากadd (x, y)=x+y(uncurried)
Jaider

คำตอบ:


872

การ Currying คือเมื่อคุณแยกฟังก์ชันที่ใช้อาร์กิวเมนต์หลาย ๆ ตัวลงในชุดฟังก์ชันที่แต่ละอาร์กิวเมนต์มีเพียงอาร์กิวเมนต์เดียวเท่านั้น นี่คือตัวอย่างใน JavaScript:

function add (a, b) {
  return a + b;
}

add(3, 4); // returns 7

นี่คือฟังก์ชันที่รับอาร์กิวเมนต์สองตัวคือ a และ b และคืนค่าผลรวม ตอนนี้เราจะทำหน้าที่ฟังก์ชั่นนี้:

function add (a) {
  return function (b) {
    return a + b;
  }
}

นี่คือฟังก์ชันที่รับอาร์กิวเมนต์หนึ่งตัวและส่งคืนฟังก์ชันที่รับอาร์กิวเมนต์อีกตัวหนึ่ง b และฟังก์ชันนั้นคืนค่าผลรวมของพวกมัน

add(3)(4);

var add3 = add(3);

add3(4);

คำสั่งแรกส่งคืน 7 เช่นคำสั่ง add (3, 4) คำสั่งที่สองกำหนดฟังก์ชั่นใหม่ที่เรียกว่า add3 ที่จะเพิ่ม 3 ลงในอาร์กิวเมนต์ของมัน นี่คือสิ่งที่บางคนอาจเรียกว่าการปิด คำสั่งที่สามใช้การดำเนินการ add3 เพื่อเพิ่ม 3 ถึง 4 สร้างผลลัพธ์ 7 อีกครั้ง


235
ในทางปฏิบัติฉันจะใช้แนวคิดนี้ได้อย่างไร
สตรอเบอร์รี่

43
@ สตรอเบอรี่พูดตัวอย่างว่าคุณมีรายการตัวเลขในจำนวน[1, 2, 3, 4, 5]ที่คุณต้องการคูณด้วยตัวเลขใด ๆ ใน Haskell ฉันสามารถเขียนmap (* 5) [1, 2, 3, 4, 5]เพื่อเพิ่มจำนวนรายการทั้งหมดด้วย5และสร้างรายการ[5, 10, 15, 20, 25]ขึ้นมา
nyson

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

78
@Strawberry อาร์กิวเมนต์แรกที่mapต้องเป็นฟังก์ชันที่ใช้เพียง 1 อาร์กิวเมนต์เท่านั้น - องค์ประกอบจากรายการ การคูณ - ตามแนวคิดทางคณิตศาสตร์ - เป็นการดำเนินการแบบไบนารี ใช้เวลา 2 ข้อโต้แย้ง อย่างไรก็ตามใน Haskell *เป็นฟังก์ชั่น curried คล้ายกับเวอร์ชั่นที่สองของaddคำตอบนี้ ผลที่ได้(* 5)คือฟังก์ชั่นที่รับอาร์กิวเมนต์เดี่ยวและคูณด้วย 5 และทำให้เราสามารถใช้กับแผนที่ได้
Doval

26
@Strawberry สิ่งที่ดีเกี่ยวกับภาษาที่ใช้งานได้เช่น Standard ML หรือ Haskell ก็คือคุณสามารถรับ "ฟรี" ได้ คุณสามารถกำหนดฟังก์ชั่นการโต้แย้งได้หลายภาษาตามที่คุณต้องการในภาษาอื่นและคุณจะได้เวอร์ชั่นที่แปลโดยอัตโนมัติโดยที่คุณไม่ต้องโยนแกะตัวเองเป็นก้อน ดังนั้นคุณสามารถสร้างฟังก์ชั่นใหม่ที่รับอาร์กิวเมนต์น้อยลงจากฟังก์ชั่นใด ๆ ที่มีอยู่โดยไม่ต้องยุ่งยากหรือรำคาญมากและทำให้มันง่ายต่อการส่งผ่านไปยังฟังก์ชันอื่น
Doval

125

ในพีชคณิตของฟังก์ชั่นการจัดการกับฟังก์ชั่นที่มีอาร์กิวเมนต์หลายตัว (หรือเทียบเท่าหนึ่งอาร์กิวเมนต์ที่เป็น N-tuple) ค่อนข้างไม่เหมาะสม - แต่ตามที่โมเสสSchönfinkel (และอิสระ Haskell Curry) พิสูจน์แล้วว่ามันไม่จำเป็น: ทั้งหมดที่คุณ ต้องการฟังก์ชั่นที่ใช้เวลาหนึ่งอาร์กิวเมนต์

ดังนั้นคุณจะจัดการกับสิ่งที่คุณแสดงออกโดยธรรมชาติว่าf(x,y)อย่างไร ดีที่คุณใช้เวลาที่เป็นเทียบเท่ากับf(x)(y)- f(x)เรียกว่าเป็นฟังก์ชั่นและคุณใช้ฟังก์ชั่นที่g yกล่าวอีกนัยหนึ่งคุณมีเพียงฟังก์ชันที่รับหนึ่งอาร์กิวเมนต์เท่านั้น แต่บางฟังก์ชันเหล่านั้นส่งคืนฟังก์ชันอื่น ๆ (ซึ่งยังใช้อาร์กิวเมนต์ตัวเดียว ;-)

ตามปกติวิกิพีเดียมีรายการสรุปที่ดีเกี่ยวกับเรื่องนี้พร้อมด้วยพอยน์เตอร์ที่มีประโยชน์มากมาย (อาจรวมถึงภาษาที่คุณชื่นชอบ ;-) รวมถึงการรักษาทางคณิตศาสตร์ที่เข้มงวดกว่าเล็กน้อย


1
ฉันคิดว่าความคิดเห็นที่คล้ายกันกับฉันข้างต้น - ฉันไม่เห็นว่าภาษาฟังก์ชั่น จำกัด ฟังก์ชั่นการ ARG เดียว ฉันเข้าใจผิด
Eric M

1
@hoohoo: ภาษาฟังก์ชั่นโดยทั่วไปไม่ได้ จำกัด ฟังก์ชั่นการโต้แย้งเดียว อย่างไรก็ตามในระดับต่ำกว่าทางคณิตศาสตร์มากขึ้นมันง่ายกว่ามากที่จะจัดการกับฟังก์ชั่นที่ใช้เพียงหนึ่งอาร์กิวเมนต์ (ในแคลคูลัสแลมบ์ดาตัวอย่างฟังก์ชั่นรับเพียงหนึ่งอาร์กิวเมนต์ในแต่ละครั้ง)
Sam DeFabbia-Kane

1
ตกลง. คำถามอื่นแล้ว ต่อไปนี้เป็นข้อความจริงหรือไม่ แลมบ์ดาแคลคูลัสสามารถใช้เป็นแบบจำลองของการเขียนโปรแกรมการทำงาน แต่การเขียนโปรแกรมการทำงานไม่จำเป็นต้องใช้แคลคูลัสแลมบ์ดา
Eric M

7
ดังที่หน้าวิกิพีเดียบันทึกภาษา FP ส่วนใหญ่ "อลงกต" หรือ "เพิ่ม" แคลคูลัสแลมบ์ดา (เช่นกับค่าคงที่และประเภทข้อมูล) มากกว่าเพียงแค่ "นำไปใช้" แต่ก็ไม่ใกล้เคียงกัน BTW อะไรที่ทำให้คุณประทับใจเช่น Haskell ไม่ได้ จำกัด ฟังก์ชั่นในการหาเรื่องหาเรื่อง? แน่นอนว่าจะเป็นเช่นนั้นแม้ว่าจะไม่เกี่ยวข้องกับการแกง เช่นdiv :: Integral a => a -> a -> a- สังเกตลูกศรหลายอัน? "แมป a ถึงฟังก์ชันการแมป a กับ" คือการอ่านหนึ่งเดียว คุณสามารถใช้อาร์กิวเมนต์ tuple (เดี่ยว) สำหรับdiv& c แต่นั่นจะเป็นการต่อต้านการใช้สำนวนใน Haskell
Alex Martelli

@Alex - wrt Haskell & arg นับฉันไม่ได้ใช้เวลามากกับ Haskell และนั่นก็เป็นเพียงไม่กี่สัปดาห์ที่ผ่านมา ดังนั้นมันจึงเป็นข้อผิดพลาดที่ง่ายที่จะทำ
Eric M

100

นี่คือตัวอย่างที่เป็นรูปธรรม:

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

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


2
ในฐานะที่เป็นความอยากรู้, ห้องสมุดต้นแบบสำหรับ JavaScript มีฟังก์ชั่น "แกง" ที่ทำสิ่งที่คุณได้อธิบายไว้ที่นี่: prototypejs.org/api/function/curry
shuckster

ลิงค์ฟังก์ชั่นแกง PrototypeJS ใหม่ prototypejs.org/doc/latest/language/Function/prototype/curry/…
Richard Ayotte

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

9
@neontapir ถูกต้อง สิ่งที่เชียอธิบายไม่ได้เป็นคำสาป มันเป็นแอพพลิเคชั่นบางส่วน ถ้าฟังก์ชั่นสามข้อโต้แย้ง curried และคุณเรียกว่าเป็น f (1) สิ่งที่คุณได้รับกลับไม่ใช่ฟังก์ชั่นสองข้อโต้แย้ง คุณได้รับฟังก์ชันหนึ่งอาร์กิวเมนต์ที่ส่งกลับฟังก์ชันอาร์กิวเมนต์หนึ่งอีกอาร์กิวเมนต์ ฟังก์ชัน curried สามารถส่งผ่านได้เพียงหนึ่งอาร์กิวเมนต์เท่านั้น ฟังก์ชั่นการแกงใน PrototypeJS ก็ไม่ได้เป็นการแกงเช่นกัน มันเป็นแอพพลิเคชั่นบางส่วน
MindJuice

ไม่ (เพื่อการประเมินผลบางส่วน) และไม่ (ที่จะ currying) นี้เรียกว่าแอปพลิเคชันบางส่วน จำเป็นต้องมีการแกงเพื่อเปิดใช้งาน
Will Ness

47

Currying คือการแปลงที่สามารถนำไปใช้กับฟังก์ชั่นเพื่อให้พวกเขาสามารถโต้แย้งน้อยลงกว่าเดิม

ตัวอย่างเช่นใน F # คุณสามารถกำหนดฟังก์ชั่นดังนี้: -

let f x y z = x + y + z

ฟังก์ชัน f ใช้พารามิเตอร์ x, y และ z และรวมเข้าด้วยกันดังนี้: -

f 1 2 3

ส่งคืน 6

จากคำจำกัดความของเราเราสามารถกำหนดฟังก์ชั่นแกงสำหรับ f: -

let curry f = fun x -> f x

โดยที่ 'fun x -> fx' คือฟังก์ชันแลมบ์ดาเทียบเท่ากับ x => f (x) ใน C # ฟังก์ชั่นนี้ใส่ฟังก์ชั่นที่คุณต้องการที่จะแกงและส่งกลับฟังก์ชั่นที่ใช้เวลาอาร์กิวเมนต์เดียวและส่งกลับฟังก์ชั่นที่ระบุด้วยการตั้งค่าอาร์กิวเมนต์แรกเป็นอาร์กิวเมนต์ป้อนเข้า

โดยใช้ตัวอย่างก่อนหน้าของเราเราสามารถได้รับแกงกะหรี่ f: -

let curryf = curry f

จากนั้นเราสามารถทำสิ่งต่อไปนี้: -

let f1 = curryf 1

ซึ่งให้ฟังก์ชัน f1 ซึ่งเทียบเท่ากับ f1 yz = 1 + y + z หมายความว่าเราสามารถทำสิ่งต่อไปนี้: -

f1 2 3

ซึ่งส่งคืน 6

กระบวนการนี้มักจะสับสนกับ 'สมัครฟังก์ชันบางส่วน' ซึ่งสามารถกำหนดได้ดังนี้: -

let papply f x = f x

แม้ว่าเราสามารถขยายไปยังพารามิเตอร์มากกว่าหนึ่งเช่น: -

let papply2 f x y = f x y
let papply3 f x y z = f x y z
etc.

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

let f1 = f 1
f1 2 3

ซึ่งจะส่งคืนผลลัพธ์เป็น 6

สรุปแล้ว:-

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

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

let f x y z = x + y + z
let curryf = curry f
let f1 = curryf 1
let f2 = curryf 2
f1 2 3
6
f2 1 3
6

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

let f x y z = x + y + z
let f1 = f 1
let f2 = f 2
f1 2 3
6
f2 1 3
6

ดังนั้นวิธีการใน C # จะต้อง curried ก่อนที่พวกเขาจะสามารถนำมาใช้บางส่วน?
cdmckay

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

44

มันอาจเป็นวิธีการใช้ฟังก์ชั่นเพื่อสร้างฟังก์ชั่นอื่น ๆ

ในจาวาสคริปต์:

let add = function(x){
  return function(y){ 
   return x + y
  };
};

จะอนุญาตให้เราเรียกมันว่าอย่างนั้น:

let addTen = add(10);

เมื่อสิ่งนี้ทำงาน10จะถูกส่งผ่านเป็นx;

let add = function(10){
  return function(y){
    return 10 + y 
  };
};

ซึ่งหมายความว่าเราจะส่งคืนฟังก์ชันนี้:

function(y) { return 10 + y };

ดังนั้นเมื่อคุณโทร

 addTen();

คุณกำลังโทรจริงๆ:

 function(y) { return 10 + y };

ดังนั้นถ้าคุณทำสิ่งนี้:

 addTen(4)

มันเหมือนกับ:

function(4) { return 10 + 4} // 14

ดังนั้นเราaddTen()มักจะเพิ่มสิบในสิ่งที่เราผ่านเราสามารถสร้างฟังก์ชันที่คล้ายกันในลักษณะเดียวกัน:

let addTwo = add(2)       // addTwo(); will add two to whatever you pass in
let addSeventy = add(70)  // ... and so on...

ตอนนี้คำถามติดตามที่ชัดเจนคือทำไมคุณต้องทำอย่างนั้นบนโลก? มันเปลี่ยนสิ่งที่เป็นการดำเนินการที่กระตือรือร้นx + yเป็นสิ่งที่สามารถก้าวผ่านความเกียจคร้านซึ่งหมายความว่าเราสามารถทำอย่างน้อยสองสิ่ง 1. แคชการดำเนินการที่มีราคาแพง 2. บรรลุ abstractions ในกระบวนทัศน์การทำงาน

ลองนึกภาพฟังก์ชั่น curried ของเราเป็นดังนี้:

let doTheHardStuff = function(x) {
  let z = doSomethingComputationallyExpensive(x)
  return function (y){
    z + y
  }
}

เราสามารถเรียกใช้ฟังก์ชั่นนี้ครั้งเดียวจากนั้นส่งผ่านผลลัพธ์ที่จะใช้ในหลาย ๆ สถานที่ซึ่งหมายความว่าเราจะทำสิ่งที่มีราคาแพงเพียงครั้งเดียว:

let finishTheJob = doTheHardStuff(10)
finishTheJob(20)
finishTheJob(30)

เราสามารถรับ abstractions ในทำนองเดียวกัน


5
คำอธิบายทีละขั้นตอนที่ดีที่สุดของกระบวนการเรียงตามลำดับที่ฉันเคยเห็นที่นี่และอาจเป็นคำตอบที่ดีที่สุดที่อธิบายได้มากที่สุด

4
@ jonsilver ฉันพูดตรงข้ามไม่ใช่คำอธิบายที่ดี ฉันเห็นด้วยที่ดีในการอธิบายตัวอย่างที่โพสต์ไว้ แต่ผู้คนมักจะเริ่มต้นคิดว่า“ ใช่ชัดเจนอย่างสมบูรณ์ แต่ฉันสามารถทำสิ่งเดียวกันได้อีกวิธีหนึ่งดังนั้นสิ่งที่ดีคือการแกงเผ็ด” ในคำอื่น ๆ ฉันหวังว่ามันมีบริบทหรือคำอธิบายที่เพียงพอที่จะให้ความสว่างไม่ใช่แค่วิธีการแกงเท่านั้น แต่ทำไมมันไม่ใช่การสังเกตที่ไร้ประโยชน์และไม่สำคัญเมื่อเทียบกับวิธีอื่นในการเพิ่มสิบ
whitneyland

29

ฟังก์ชั่น curried เป็นฟังก์ชั่นของข้อโต้แย้งหลายข้อที่เขียนใหม่เพื่อให้ยอมรับอาร์กิวเมนต์แรกและส่งกลับฟังก์ชันที่ยอมรับอาร์กิวเมนต์ที่สองเป็นต้น สิ่งนี้อนุญาตให้ฟังก์ชันของอาร์กิวเมนต์หลายตัวมีข้อโต้แย้งเริ่มต้นบางส่วนที่นำมาใช้บางส่วน


5
"สิ่งนี้ช่วยให้ฟังก์ชันของอาร์กิวเมนต์หลายตัวมีข้อโต้แย้งเริ่มต้นบางส่วนที่นำมาใช้บางส่วน" - ทำไมจึงเป็นประโยชน์
acarlon

5
@acarlon ฟังก์ชั่นมักจะถูกเรียกซ้ำ ๆ กันด้วยข้อโต้แย้งหนึ่งข้อขึ้นไป ตัวอย่างเช่นถ้าคุณต้องการmapฟังก์ชั่นfมากกว่ารายการแสดงรายการที่คุณสามารถทำได้xss map (map f) xss
Jon Harrop

1
ขอบคุณที่ทำให้รู้สึก ฉันอ่านเพิ่มอีกนิดและมันก็เข้าที่
acarlon

4
ฉันคิดว่าคำตอบนี้ทำให้ถูกต้องในวิธีที่กระชับดี "currying" คือกระบวนการรับฟังก์ชั่นของการโต้แย้งหลาย ๆ ครั้งและแปลงมันเป็นฟังก์ชั่นที่จริงจังซึ่งแต่ละข้อโต้แย้งเพียงครั้งเดียวและส่งกลับฟังก์ชั่นของข้อโต้แย้งเดียวหรือในกรณีของฟังก์ชั่นสุดท้ายคืนผลลัพธ์ที่แท้จริง . คุณสามารถทำสิ่งนี้ได้โดยอัตโนมัติด้วยภาษาหรือคุณสามารถเรียกใช้ฟังก์ชั่นแกงกะหรี่ () ในภาษาอื่นเพื่อสร้างเวอร์ชั่นแกงกะหรี่ โปรดทราบว่าการเรียกใช้ฟังก์ชั่น curried พร้อมพารามิเตอร์ไม่ได้เป็นการ currying แกงเกิดขึ้นแล้ว
MindJuice

7

นี่คือตัวอย่างของเล่นใน Python:

>>> from functools import partial as curry

>>> # Original function taking three parameters:
>>> def display_quote(who, subject, quote):
        print who, 'said regarding', subject + ':'
        print '"' + quote + '"'


>>> display_quote("hoohoo", "functional languages",
           "I like Erlang, not sure yet about Haskell.")
hoohoo said regarding functional languages:
"I like Erlang, not sure yet about Haskell."

>>> # Let's curry the function to get another that always quotes Alex...
>>> am_quote = curry(display_quote, "Alex Martelli")

>>> am_quote("currying", "As usual, wikipedia has a nice summary...")
Alex Martelli said regarding currying:
"As usual, wikipedia has a nice summary..."

(เพียงใช้การต่อข้อมูลผ่าน + เพื่อหลีกเลี่ยงสิ่งรบกวนสำหรับโปรแกรมเมอร์ที่ไม่ใช่ Python)

การแก้ไขเพื่อเพิ่ม:

ดูที่http://docs.python.org/library/functools.html?highlight=partial#functools.partialซึ่งแสดงวัตถุบางส่วนเทียบกับความแตกต่างของฟังก์ชันในวิธีที่ Python ใช้สิ่งนี้


ฉันไม่ได้รับสิ่งนี้ - คุณทำสิ่งนี้: >>> am_quote = curry (display_quote, "Alex Martelli") แต่จากนั้นคุณทำสิ่งต่อไปนี้: >>> am_quote ("currying", "ตามปกติวิกิพีเดียมีบทสรุปที่ดี .. ") คุณมีฟังก์ชั่นที่มีสองส่วน ดูเหมือนว่าการแกงควรให้ funcs ที่แตกต่างกันสามแบบที่คุณจะแต่ง?
Eric M

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

(BTW - 'การ >>>' เป็นพรอมต์ในล่ามโต้ตอบหลามไม่เป็นส่วนหนึ่งของรหัส.)
อานนท์

ตกลงขอบคุณสำหรับคำชี้แจงเกี่ยวกับ args ฉันรู้เกี่ยวกับงูหลามล่ามพรอมต์ผมพยายามที่จะพูดสาย แต่มัน diidn't ทำงาน ;-)
เอริคเอ็ม

หลังจากความคิดเห็นของคุณฉันค้นหาและพบการอ้างอิงอื่น ๆ รวมถึงที่นี่ใน SO เพื่อความแตกต่างระหว่าง "แกง" และ "แอปพลิเคชันบางส่วน" เพื่อตอบสนองต่ออินสแตนซ์จำนวนมากของการใช้งานที่ไม่แน่ชัดที่ฉันคุ้นเคย ดูตัวอย่าง: stackoverflow.com/questions/218025/…
Anon

5

ความดีความชอบเป็นฟังก์ชั่นแปลจาก callable เป็นf(a, b, c)เข้า callable f(a)(b)(c)เป็น

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

แท้จริงแล้วการแกงเป็นการเปลี่ยนแปลงของฟังก์ชั่น: จากวิธีหนึ่งในการโทรไปยังอีกวิธีหนึ่ง ใน JavaScript เรามักจะทำ wrapper เพื่อคงฟังก์ชั่นดั้งเดิมเอาไว้

การแกงไม่เรียกฟังก์ชั่น มันแค่แปลงมัน

ลองสร้างฟังก์ชั่นการแกงที่ทำหน้าที่แกงสำหรับสองข้อโต้แย้ง กล่าวอีกนัยหนึ่งcurry(f)สำหรับสองข้อโต้แย้งf(a, b)แปลมันเป็นf(a)(b)

function curry(f) { // curry(f) does the currying transform
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

// usage
function sum(a, b) {
  return a + b;
}

let carriedSum = curry(sum);

alert( carriedSum(1)(2) ); // 3

อย่างที่คุณเห็นการใช้งานเป็นชุดของตัวห่อ

  • ผลลัพธ์ของการcurry(func)เป็น wrapperfunction(a)เป็นเสื้อคลุม
  • เมื่อมันถูกเรียกว่าsum(1)อาร์กิวเมนต์จะถูกบันทึกใน Lexical Environment และจะส่งคืน wrapper ใหม่function(b)อาร์กิวเมนต์จะถูกบันทึกไว้ในคำศัพท์สิ่งแวดล้อมและเสื้อคลุมใหม่จะถูกส่งกลับ
  • จากนั้นจึงsum(1)(2)เรียกfunction(b)การจัดเตรียม 2 และผ่านการเรียกไปยังผลรวมของอาร์กิวเมนต์หลายตัวดั้งเดิม

4

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

ใน Clojure +เป็นฟังก์ชั่น แต่จะทำให้สิ่งต่าง ๆ ชัดเจน:

(defn add [a b] (+ a b))

คุณอาจทราบว่าincฟังก์ชั่นนั้นเพิ่ม 1 เข้าไปในหมายเลขใด ๆ ที่มันผ่าน

(inc 7) # => 8

มาสร้างมันเองโดยใช้partial:

(def inc (partial add 1))

ที่นี่เรากลับฟังก์ชั่นที่ได้ 1 addโหลดลงในอาร์กิวเมนต์แรกของ ในฐานะที่เป็นaddเวลาสองขัดแย้งใหม่incฟังก์ชั่นที่ต้องการเพียงbการโต้แย้ง - ไม่ 2 ข้อโต้แย้งเป็นมาก่อนตั้งแต่วันที่ 1 ได้รับแล้วบางส่วนนำไปใช้ ดังนั้นจึงpartialเป็นเครื่องมือในการสร้างฟังก์ชั่นใหม่ที่มีการกำหนดค่าเริ่มต้นไว้ล่วงหน้า นั่นคือเหตุผลที่ฟังก์ชั่นภาษาที่ใช้งานได้มักจะเรียงลำดับอาร์กิวเมนต์จากทั่วไปถึงเฉพาะ สิ่งนี้ทำให้ง่ายต่อการใช้ฟังก์ชั่นดังกล่าวเพื่อสร้างฟังก์ชั่นอื่น ๆ

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

(def inc (add 1)) #partial is implied

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


3

ฉันพบบทความนี้และบทความที่อ้างอิงมีประโยชน์เพื่อทำความเข้าใจกับการแกง: http://blogs.msdn.com/wesdyer/archive/2007/01/29/currying-and-partial-application.aspx

ดังที่คนอื่น ๆ กล่าวถึงมันเป็นเพียงวิธีหนึ่งที่จะมีฟังก์ชั่นพารามิเตอร์เดียว

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


3

ในฐานะที่เป็นคำตอบอื่น ๆ ทั้งหมด currying ช่วยในการสร้างฟังก์ชั่นการใช้งานบางส่วน Javascript ไม่ได้ให้การสนับสนุนดั้งเดิมสำหรับการแกงอัตโนมัติ ตัวอย่างที่ให้ไว้ข้างต้นอาจไม่ช่วยในการเขียนโปรแกรมเชิงปฏิบัติ มีตัวอย่างที่ยอดเยี่ยมบางส่วนใน livescript (ซึ่งเป็นหลักในการรวบรวม js) http://livescript.net/

times = (x, y) --> x * y
times 2, 3       #=> 6 (normal use works as expected)
double = times 2
double 5         #=> 10

ในตัวอย่างด้านบนเมื่อคุณได้รับข้อโต้แย้งไลฟ์สทริบิวชั่นน้อยสร้างฟังก์ชัน curried ใหม่สำหรับคุณ


3

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

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

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

ตัวอย่างเช่น:

function curryMinus(x) 
{
  return function(y) 
  {
    return x - y;
  }
}

var minus5 = curryMinus(1);
minus5(3);
minus5(5);

ฉันสามารถทำ ...

var minus7 = curryMinus(7);
minus7(3);
minus7(5);

สิ่งนี้ยอดเยี่ยมมากสำหรับการทำโค้ดที่ซับซ้อนให้เรียบร้อยและการจัดการกับวิธีการที่ไม่ซิงโครไนซ์เป็นต้น


2

ฟังก์ชั่น curried ถูกนำไปใช้กับรายการอาร์กิวเมนต์หลายรายการแทนที่จะเป็นเพียงรายการเดียว

นี่คือฟังก์ชั่นปกติที่ไม่ใช่ curried ซึ่งเพิ่มพารามิเตอร์ Int สองตัวคือ x และ y:

scala> def plainOldSum(x: Int, y: Int) = x + y
plainOldSum: (x: Int,y: Int)Int
scala> plainOldSum(1, 2)
res4: Int = 3

นี่คือฟังก์ชั่นที่คล้ายกันที่ curried แทนที่จะใช้หนึ่งรายการของสองพารามิเตอร์ Int คุณสามารถใช้ฟังก์ชันนี้กับสองรายการของหนึ่งพารามิเตอร์ Int แต่ละตัว:

scala> def curriedSum(x: Int)(y: Int) = x + y
curriedSum: (x: Int)(y: Int)Intscala> second(2)
res6: Int = 3
scala> curriedSum(1)(2)
res5: Int = 3

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

นี่คือฟังก์ชั่นที่ตั้งชื่อโดยfirstคำนึงถึงสิ่งที่การเรียกใช้ฟังก์ชันดั้งเดิมครั้งแรกcurriedSumจะทำ:

scala> def first(x: Int) = (y: Int) => x + y
first: (x: Int)(Int) => Int

การนำ 1 ไปใช้กับฟังก์ชันแรก - กล่าวอีกนัยหนึ่งคือเรียกใช้ฟังก์ชันแรกและผ่านไปใน 1 - ให้ฟังก์ชันที่สอง:

scala> val second = first(1)
second: (Int) => Int = <function1>

การใช้ 2 กับฟังก์ชันที่สองจะให้ผลลัพธ์:

scala> second(2)
res6: Int = 3

2

ตัวอย่างของการ currying คือเมื่อมีฟังก์ชั่นที่คุณรู้เพียงหนึ่งในพารามิเตอร์ในขณะนี้:

ตัวอย่างเช่น:

func aFunction(str: String) {
    let callback = callback(str) // signature now is `NSData -> ()`
    performAsyncRequest(callback)
}

func callback(str: String, data: NSData) {
    // Callback code
}

func performAsyncRequest(callback: NSData -> ()) {
    // Async code that will call callback with NSData as parameter
}

ที่นี่เนื่องจากคุณไม่ทราบพารามิเตอร์ที่สองสำหรับการโทรกลับเมื่อส่งถึงperformAsyncRequest(_:)คุณคุณจะต้องสร้างแลมบ์ดา / ปิดอีกอันเพื่อส่งหนึ่งไปยังฟังก์ชัน


จะfunc callbackกลับมา? มันถูกเรียกว่า @ callback(str)ดังนั้นการlet callback = callback(str)เรียกกลับเป็นเพียงค่าตอบแทนของfunc callback
nikk wong

ไม่func callback(_:data:)ยอมรับพารามิเตอร์สองตัวที่นี่ฉันให้มันเพียงอันเดียวStringดังนั้นมันจึงรออีกอัน ( NSData) นี่คือเหตุผลว่าทำไมตอนนี้let callbackฟังก์ชั่นอื่นกำลังรอข้อมูลที่จะส่งผ่าน
S2dent

2

นี่คือตัวอย่างของเวอร์ชันทั่วไปและเวอร์ชันที่สั้นที่สุดสำหรับฟังก์ชัน currying ที่ไม่มี n ของ params

const add = a => b => b ? add(a + b) : a; 

const add = a => b => b ? add(a + b) : a; 
console.log(add(1)(2)(3)(4)());


1

ที่นี่คุณจะพบคำอธิบายง่ายๆเกี่ยวกับการใช้งาน currying ใน C # ในความคิดเห็นฉันพยายามแสดงให้เห็นว่าการแกงมีประโยชน์อย่างไร:

public static class FuncExtensions {
    public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
    {
        return x1 => x2 => func(x1, x2);
    }
}

//Usage
var add = new Func<int, int, int>((x, y) => x + y).Curry();
var func = add(1);

//Obtaining the next parameter here, calling later the func with next parameter.
//Or you can prepare some base calculations at the previous step and then
//use the result of those calculations when calling the func multiple times 
//with different input parameters.

int result = func(1);

1

Currying เป็นหนึ่งในฟังก์ชันลำดับสูงของ Java Script

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

สับสน?

ลองดูตัวอย่าง

function add(a,b)
    {
        return a+b;
    }
add(5,6);

คล้ายกับฟังก์ชั่นการแกงต่อไปนี้

function add(a)
    {
        return function(b){
            return a+b;
        }
    }
var curryAdd = add(5);
curryAdd(6);

ดังนั้นรหัสนี้หมายความว่าอย่างไร

ตอนนี้อ่านคำจำกัดความอีกครั้ง

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

ยังสับสนอยู่ไหม? ให้ฉันอธิบายอย่างลึกซึ้ง!

เมื่อคุณเรียกใช้ฟังก์ชันนี้

var curryAdd = add(5);

มันจะคืนค่าฟังก์ชันเช่นนี้ให้กับคุณ

curryAdd=function(y){return 5+y;}

ดังนั้นนี่เรียกว่าฟังก์ชันลำดับที่สูงกว่า ความหมายการเรียกใช้ฟังก์ชั่นหนึ่งในผลตอบแทนที่ส่งกลับฟังก์ชั่นอื่นเป็นคำนิยามที่แน่นอนสำหรับฟังก์ชั่นการสั่งซื้อที่สูงขึ้น นี่เป็นข้อได้เปรียบที่ยิ่งใหญ่ที่สุดสำหรับ Java Script ดังนั้นกลับมาที่การแกง

บรรทัดนี้จะส่งผ่านอาร์กิวเมนต์ที่สองไปยังฟังก์ชัน curryAdd

curryAdd(6);

ซึ่งจะส่งผลให้

curryAdd=function(6){return 5+6;}
// Which results in 11

หวังว่าคุณจะเข้าใจการใช้งานของแกงที่นี่ ดังนั้นมาถึงข้อดี

ทำไมต้องแกง

มันทำให้การใช้งานของรหัสนำมาใช้ รหัสน้อยลง, ข้อผิดพลาดน้อยลง คุณอาจถามว่ามันเป็นรหัสน้อยได้อย่างไร

ฉันสามารถพิสูจน์ได้ด้วยฟังก์ชันลูกศรคุณลักษณะใหม่ของ ECMA script 6

ใช่ ECMA 6 มอบคุณลักษณะที่ยอดเยี่ยมที่เรียกว่าฟังก์ชั่นลูกศร

function add(a)
    {
        return function(b){
            return a+b;
        }
    }

ด้วยความช่วยเหลือของฟังก์ชั่นลูกศรเราสามารถเขียนฟังก์ชั่นด้านบนดังนี้

x=>y=>x+y

เจ๋งใช่มั้ย

ดังนั้นรหัสน้อยและข้อบกพร่องน้อยลง !!

ด้วยความช่วยเหลือของฟังก์ชั่นการสั่งซื้อที่สูงขึ้นเหล่านี้คุณสามารถพัฒนาโค้ดที่ปราศจากข้อบกพร่องได้อย่างง่ายดาย

ฉันขอท้าคุณ!

หวังว่าคุณเข้าใจสิ่งที่กำลังร้องไห้ โปรดแสดงความคิดเห็นที่นี่หากคุณต้องการคำชี้แจงใด ๆ

ขอบคุณมีวันที่ดี!


0

มีตัวอย่างของ "Currying in ReasonML"

let run = () => {
    Js.log("Curryed function: ");
    let sum = (x, y) => x + y;
    Printf.printf("sum(2, 3) : %d\n", sum(2, 3));
    let per2 = sum(2);
    Printf.printf("per2(3) : %d\n", per2(3));
  };
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.