ฉันอ่านฟังก์ชั่นสกาล่า (เป็นส่วนหนึ่งของทัวร์สกาล่าอีกครั้ง ) ในโพสต์ที่เขาระบุว่า:
วิธีการและฟังก์ชั่นไม่เหมือนกัน
แต่เขาไม่ได้อธิบายอะไรเกี่ยวกับมัน เขากำลังพยายามจะพูดอะไร
ฉันอ่านฟังก์ชั่นสกาล่า (เป็นส่วนหนึ่งของทัวร์สกาล่าอีกครั้ง ) ในโพสต์ที่เขาระบุว่า:
วิธีการและฟังก์ชั่นไม่เหมือนกัน
แต่เขาไม่ได้อธิบายอะไรเกี่ยวกับมัน เขากำลังพยายามจะพูดอะไร
คำตอบ:
จิมได้รับเรื่องนี้ค่อนข้างครอบคลุมในโพสต์บล็อกของเขาแต่ฉันโพสต์บรรยายสรุปที่นี่เพื่อการอ้างอิง
ก่อนอื่นเรามาดูกันว่าสเปคของสกาล่าบอกอะไรเราบ้าง บทที่ 3 (ประเภท) บอกเราเกี่ยวกับประเภทฟังก์ชั่น (3.2.9) และประเภทวิธี (3.3.1) บทที่ 4 (การประกาศพื้นฐาน) พูดถึงการประกาศมูลค่าและคำจำกัดความ (4.1) การประกาศตัวแปรและคำจำกัดความ (4.2) และการประกาศฟังก์ชั่นและคำจำกัดความ (4.6) บทที่ 6 (การแสดงออก) พูดถึงฟังก์ชั่นที่ไม่ระบุชื่อ (6.23) และค่าวิธีการ (6.7) อยากรู้อยากเห็นค่าฟังก์ชั่นจะพูดครั้งเดียวใน 3.2.9 และไม่มีที่ไหนอีก
ประเภทฟังก์ชั่นคือ (ประมาณ) ประเภทของรูปแบบ(T1, ... , TN) => Uซึ่งเป็นชวเลขสำหรับลักษณะที่FunctionN
อยู่ในห้องสมุดมาตรฐาน ฟังก์ชั่นที่ไม่ระบุชื่อและค่าวิธีการมีประเภทฟังก์ชั่นและประเภทฟังก์ชั่นสามารถนำมาใช้เป็นส่วนหนึ่งของค่าการประกาศตัวแปรและฟังก์ชั่นและคำจำกัดความ ในความเป็นจริงมันสามารถเป็นส่วนหนึ่งของชนิดวิธี
วิธีประเภทคือประเภทที่ไม่คุ้มค่า นั่นหมายความว่าไม่มีค่า - ไม่มีวัตถุไม่มีอินสแตนซ์ - ด้วยชนิดของวิธีการ ดังกล่าวข้างต้นเป็นราคาวิธีจริงมีประเภทฟังก์ชั่น ประเภทวิธีการคือการdef
ประกาศ - ทุกอย่างเกี่ยวกับdef
ยกเว้นร่างกายของมัน
ราคาประกาศและคำนิยามและการประกาศตัวแปรและนิยามอยู่val
และvar
ประกาศรวมทั้งชนิดและความคุ้มค่า - ซึ่งจะเป็นไปตามลำดับประเภทฟังก์ชั่นและไม่เปิดเผยตัวฟังก์ชั่นหรือค่าวิธี โปรดทราบว่าใน JVM ค่าเหล่านี้ (ค่าวิธีการ) จะถูกนำไปใช้กับสิ่งที่ Java เรียกว่า "วิธีการ"
ประกาศฟังก์ชั่น คือdef
การประกาศรวมทั้งชนิดและร่างกาย ส่วนประเภทคือประเภทวิธีและร่างกายเป็นแสดงออกหรือบล็อก สิ่งนี้ถูกนำไปใช้กับ JVM ด้วยสิ่งที่ Java เรียกว่า "เมธอด"
ในที่สุดฟังก์ชั่นที่ไม่ระบุชื่อเป็นตัวอย่างของฟังก์ชั่นประเภท (เช่นอินสแตนซ์ของลักษณะFunctionN
) และค่าวิธีการเป็นสิ่งเดียวกัน! ความแตกต่างคือว่าค่าวิธีการที่ถูกสร้างขึ้นจากวิธีการอย่างใดอย่างหนึ่งโดยการ postfixing ขีดล่าง ( m _
เป็นค่าวิธีการที่สอดคล้องกับ "ประกาศฟังก์ชั่น" ( def
) m
) หรือโดยกระบวนการที่เรียกว่าการขยายตัว etaซึ่งเป็นเหมือน ฟังก์ชั่น
นั่นคือสิ่งที่สเป็คพูดดังนั้นขอผมพูดแบบนี้เถอะ: เราไม่ใช้คำศัพท์นั้น! มันนำไปสู่ความสับสนมากเกินไประหว่างสิ่งที่เรียกว่า"การประกาศฟังก์ชั่น"ซึ่งเป็นส่วนหนึ่งของโปรแกรม (บทที่ 4 - การประกาศพื้นฐาน) และ"ฟังก์ชั่นที่ไม่ระบุชื่อ"ซึ่งเป็นนิพจน์และ"ประเภทฟังก์ชั่น"ซึ่งก็คือ ทั้งประเภท - ลักษณะ
คำศัพท์ด้านล่างและใช้โดยโปรแกรมเมอร์ Scala ประสบการณ์ทำให้คนเปลี่ยนจากคำศัพท์ของข้อกำหนดนี้: แทนที่จะพูดว่าการประกาศฟังก์ชันเราบอกวิธีการ หรือแม้กระทั่งการประกาศวิธีการ นอกจากนี้เรายังทราบว่าการประกาศค่าและการประกาศตัวแปรเป็นวิธีการที่ใช้งานได้จริง
ดังนั้นเมื่อมีการเปลี่ยนแปลงคำศัพท์ข้างต้นนี่เป็นคำอธิบายที่ใช้งานได้จริงของความแตกต่าง
ฟังก์ชั่นเป็นวัตถุที่มีหนึ่งที่FunctionX
มีลักษณะเช่นFunction0
, Function1
, Function2
ฯลฯ มันอาจจะรวมถึงเช่นกันซึ่งจริงขยายPartialFunction
Function1
ลองดูประเภทลายเซ็นของหนึ่งในคุณสมบัติเหล่านี้:
trait Function2[-T1, -T2, +R] extends AnyRef
ลักษณะนี้มีวิธีนามธรรม (มีวิธีที่เป็นรูปธรรมเช่นกัน):
def apply(v1: T1, v2: T2): R
และนั่นบอกพวกเราทุกคนว่าต้องรู้อะไรเกี่ยวกับมัน ฟังก์ชั่นมีapply
วิธีการที่ได้รับไม่มีค่าพารามิเตอร์ของประเภทT1 , T2 , ... , เทนเนสซีR
และบางสิ่งบางอย่างจากประเภทผลตอบแทน มันเป็นตัวแปรที่ตรงกันข้ามกับพารามิเตอร์ที่ได้รับและตัวแปรร่วมกับผลลัพธ์
ที่แปรปรวนหมายความว่าเป็นชนิดย่อยของFunction1[Seq[T], String]
Function1[List[T], AnyRef]
การเป็นประเภทย่อยหมายถึงมันสามารถใช้แทนมันได้ หนึ่งสามารถเห็นได้อย่างง่ายดายว่าถ้าฉันจะโทรf(List(1, 2, 3))
และคาดว่าจะAnyRef
กลับมาทั้งสองประเภทข้างต้นจะทำงาน
ทีนี้ความคล้ายคลึงกันของเมธอดและฟังก์ชั่นคืออะไร? ทีนี้ถ้าf
เป็นฟังก์ชั่นและm
เป็นวิธีการภายในขอบเขตแล้วทั้งสองสามารถเรียกเช่นนี้:
val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))
การเรียกเหล่านี้จะแตกต่างกันจริง ๆ เพราะสิ่งแรกคือน้ำตาลแบบวากยสัมพันธ์ สกาล่าขยายไปที่:
val o1 = f.apply(List(1, 2, 3))
f
ซึ่งแน่นอนเป็นวิธีการเรียกบนวัตถุ ฟังก์ชั่นยังมีน้ำตาลในประโยคอื่น ๆ เพื่อประโยชน์ของมัน: ตัวอักษรฟังก์ชั่น (สองของพวกเขาจริง) และ(T1, T2) => R
ลายเซ็นประเภท ตัวอย่างเช่น:
val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
case i: Int => "Int"
case d: Double => "Double"
case o => "Other"
}
ความคล้ายคลึงกันระหว่างเมธอดและฟังก์ชั่นก็คือความสามารถในอดีตที่สามารถแปลงเป็นหลัง:
val f = m _
Scala จะขยายตัวที่สมมติว่าm
ชนิด(List[Int])AnyRef
เข้า (Scala 2.7):
val f = new AnyRef with Function1[List[Int], AnyRef] {
def apply(x$1: List[Int]) = this.m(x$1)
}
บน Scala 2.8 มันใช้AbstractFunction1
คลาสเพื่อลดขนาดคลาส
โปรดสังเกตว่าไม่มีวิธีแปลงวิธีอื่น ๆ - จากฟังก์ชันเป็นวิธี
อย่างไรก็ตามวิธีการมีข้อได้เปรียบที่ยิ่งใหญ่อย่างหนึ่ง (ดีสอง - พวกเขาสามารถเร็วกว่าเล็กน้อย): พวกเขาสามารถรับพารามิเตอร์ประเภทได้ ตัวอย่างเช่นในขณะที่f
ข้างต้นจำเป็นต้องระบุประเภทของList
มันได้รับ ( List[Int]
ในตัวอย่าง) m
สามารถ parameterize มัน:
def m[T](l: List[T]): String = l mkString ""
ฉันคิดว่ามันค่อนข้างครอบคลุมทุกอย่าง แต่ฉันยินดีที่จะเติมเต็มด้วยคำตอบสำหรับคำถามที่อาจยังคงอยู่
val f = m
คอมไพเลอร์ตามที่val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
คุณควรชี้ให้เห็นว่าวิธีthis
การภายในapply
ไม่ได้อ้างอิงถึงAnyRef
วัตถุ แต่ไปยังวัตถุที่มีการval f = m _
ประเมินวิธีการ( ด้านนอก this
เพื่อที่จะพูด ) เนื่องจากthis
เป็นค่าที่ถูกปิดโดยการปิด (เช่นเช่นreturn
ตามที่ระบุไว้ด้านล่าง)
ความแตกต่างในทางปฏิบัติที่สำคัญอย่างหนึ่งระหว่างเมธอดและฟังก์ชันคือความreturn
หมาย return
ผลตอบแทนที่ได้จากวิธีการเดียวเท่านั้น ตัวอย่างเช่น:
scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
val f = () => { return "test" }
^
กลับมาจากฟังก์ชั่นที่กำหนดไว้ในวิธีการที่จะส่งกลับไม่ใช่ท้องถิ่น:
scala> def f: String = {
| val g = () => { return "test" }
| g()
| "not this"
| }
f: String
scala> f
res4: String = test
ในขณะที่ส่งคืนจากเมธอดโลคัลส่งคืนจากเมธอดนั้นเท่านั้น
scala> def f2: String = {
| def g(): String = { return "test" }
| g()
| "is this"
| }
f2: String
scala> f2
res5: String = is this
for (a <- List(1, 2, 3)) { return ... }
? ที่ได้รับ de-sugared เพื่อปิด
return
คืนค่าจากฟังก์ชั่นและบางรูปแบบescape
หรือbreak
หรือcontinue
เพื่อกลับจากวิธีการ
ฟังก์ชันสามารถเรียกใช้ฟังก์ชันพร้อมรายการอาร์กิวเมนต์เพื่อสร้างผลลัพธ์ ฟังก์ชั่นมีรายการพารามิเตอร์ร่างกายและประเภทผลลัพธ์ ฟังก์ชั่นที่เป็นสมาชิกของคลาสลักษณะหรือวัตถุเดี่ยวจะเรียกว่าวิธีการ ฟังก์ชั่นที่กำหนดไว้ภายในฟังก์ชั่นอื่น ๆ เรียกว่าฟังก์ชั่นท้องถิ่น ฟังก์ชั่นที่มีประเภทผลลัพธ์ของหน่วยที่เรียกว่าขั้นตอน ฟังก์ชั่นไม่ระบุชื่อในรหัสที่มาจะเรียกว่าฟังก์ชั่นตัวอักษร ณ รันไทม์ตัวอักษรฟังก์ชันถูกยกตัวอย่างเป็นวัตถุที่เรียกว่าค่าฟังก์ชัน
การเขียนโปรแกรมใน Scala Second Edition Martin Odersky - Lex Spoon - Bill Venners
สมมติว่าคุณมีรายการ
scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
กำหนดวิธีการ
scala> def m1(i:Int)=i+2
m1: (i: Int)Int
กำหนดฟังก์ชั่น
scala> (i:Int)=>i+2
res0: Int => Int = <function1>
scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)
วิธีการรับอาร์กิวเมนต์
scala> m1(2)
res3: Int = 4
กำหนดฟังก์ชั่นด้วยวาล
scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>
อาร์กิวเมนต์ฟังก์ชั่นเป็นตัวเลือก
scala> p(2)
res4: Int = 4
scala> p
res5: Int => Int = <function1>
อาร์กิวเมนต์ของเมธอดคือข้อบังคับ
scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function
ตรวจสอบบทช่วยสอนต่อไปนี้ที่อธิบายถึงการส่งผ่านความแตกต่างอื่น ๆ ด้วยตัวอย่างเช่นตัวอย่างอื่นของ diff กับฟังก์ชั่น Method Vs, การใช้ฟังก์ชั่นเป็น Variables, การสร้างฟังก์ชันที่ส่งคืนฟังก์ชัน
ฟังก์ชั่นไม่รองรับค่าเริ่มต้นของพารามิเตอร์ วิธีการทำ การแปลงจากเมธอดไปยังฟังก์ชันจะสูญเสียค่าดีฟอลต์ของพารามิเตอร์ (สกาล่า 2.8.1)
มีบทความที่ดีที่นี่ซึ่งคำอธิบายของฉันส่วนใหญ่ถูกนำมา เปรียบเทียบฟังก์ชั่นและวิธีการสั้น ๆ เกี่ยวกับความเข้าใจของฉัน หวังว่าจะช่วย:
ฟังก์ชั่น : พวกมันเป็นวัตถุ แม่นยำมากขึ้นฟังก์ชั่นเป็นวัตถุที่มีวิธีการใช้; ดังนั้นจึงช้ากว่าวิธีเล็กน้อยเนื่องจากค่าใช้จ่าย มันคล้ายกับวิธีการคงที่ในแง่ที่ว่าพวกเขาเป็นอิสระจากวัตถุที่จะเรียก ตัวอย่างง่ายๆของฟังก์ชั่นก็เหมือนกับการร้อง:
val f1 = (x: Int) => x + x
f1(2) // 4
บรรทัดข้างต้นไม่มีอะไรยกเว้นการกำหนดวัตถุหนึ่งให้กับอีกวัตถุหนึ่งเช่น object1 = object2 อันที่จริง object2 ในตัวอย่างของเราคือฟังก์ชันที่ไม่ระบุตัวตนและด้านซ้ายได้รับชนิดของวัตถุเนื่องจากสิ่งนั้น ดังนั้นตอนนี้ f1 เป็นวัตถุ (ฟังก์ชั่น) ฟังก์ชั่นที่ไม่ระบุชื่อเป็นจริงตัวอย่างของ Function1 [Int, Int] ที่หมายถึงฟังก์ชั่นที่มี 1 พารามิเตอร์ประเภท int และค่าตอบแทนประเภท int การเรียก f1 โดยไม่มีข้อโต้แย้งจะทำให้เรามีลายเซ็นของฟังก์ชันที่ไม่ระบุตัวตน (Int => Int =)
วิธีการ : พวกมันไม่ใช่วัตถุ แต่ถูกกำหนดให้กับอินสแตนซ์ของคลาสเช่นวัตถุ เหมือนกับวิธีการในฟังก์ชั่น java หรือสมาชิกใน c ++ (ตามที่Raffi Khatchadourianชี้ให้เห็นในการแสดงความคิดเห็นต่อคำถามนี้ ) และอื่น ๆ ตัวอย่างง่ายๆของวิธีนี้ก็เหมือนกับการร้อง:
def m1(x: Int) = x + x
m1(2) // 4
บรรทัดข้างต้นไม่ใช่การกำหนดค่าอย่างง่าย แต่เป็นคำจำกัดความของวิธีการ เมื่อคุณเรียกใช้เมธอดนี้ด้วยค่า 2 เหมือนกับบรรทัดที่สอง x จะถูกแทนที่ด้วย 2 และผลลัพธ์จะถูกคำนวณและคุณจะได้รับ 4 เป็นเอาต์พุต ที่นี่คุณจะได้รับข้อผิดพลาดหากเพียงแค่เขียน m1 เพราะเป็นวิธีการและต้องการค่าอินพุต โดยใช้ _ คุณสามารถกำหนดวิธีการให้กับฟังก์ชั่นการร้อง:
val f2 = m1 _ // Int => Int = <function1>
นี่คือโพสต์ที่ยอดเยี่ยมโดย Rob Norris ซึ่งอธิบายความแตกต่างนี่คือ TL; DR
เมธอดใน Scala ไม่ใช่ค่า แต่เป็นฟังก์ชั่น คุณสามารถสร้างฟังก์ชั่นที่มอบหมายให้วิธีการผ่านการขยายตัว ((เรียกโดยสิ่งที่ต่อท้ายขีดเส้นใต้)
ด้วยคำจำกัดความต่อไปนี้:
วิธีเป็นสิ่งที่กำหนดไว้กับdefและคุ้มค่าเป็นสิ่งที่คุณสามารถกำหนดให้กับVal
สรุป ( แยกจากบล็อก ):
val
เมื่อเรากำหนดวิธีการที่เราจะเห็นว่าเราไม่สามารถกำหนดให้
scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int
scala> val f = add1
<console>:8: error: missing arguments for method add1;
follow this method with `_' if you want to treat it as a partially applied function
val f = add1
โปรดทราบประเภทของadd1
ซึ่งไม่ดูปกติ (n: Int)Int
คุณไม่สามารถประกาศตัวแปรของชนิด วิธีการไม่ใช่ค่า
อย่างไรก็ตามโดยการเพิ่มตัวดำเนินการ postfix (- ขยาย (pron เด่นชัดคือ "eta") เราสามารถเปลี่ยนวิธีการเป็นค่าฟังก์ชัน หมายเหตุ: f
ประเภทของ
scala> val f = add1 _
f: Int => Int = <function1>
scala> f(3)
res0: Int = 4
ผลกระทบของ_
คือการดำเนินการเทียบเท่าต่อไปนี้: เราสร้างFunction1
อินสแตนซ์ที่มอบหมายให้วิธีการของเรา
scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>
scala> g(3)
res18: Int = 4
ใน Scala 2.13 ซึ่งแตกต่างจากฟังก์ชั่นวิธีการสามารถรับ / คืน
อย่างไรก็ตามข้อ จำกัด เหล่านี้จะยกเป็นจุด (Scala 3) โดยฟังก์ชันประเภท Polymorphic # 4672ตัวอย่างเช่นรุ่น dotty 0.23.0-RC1เปิดใช้งานไวยากรณ์ต่อไปนี้
พิมพ์พารามิเตอร์
def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))
พารามิเตอร์โดยนัย ( พารามิเตอร์บริบท )
def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero
ประเภทขึ้นอยู่กับ
class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet
สำหรับตัวอย่างเพิ่มเติมดูการทดสอบ / run / polymorphic-functions.scala
ในทางปฏิบัติโปรแกรมเมอร์ของ Scala ต้องการเพียงรู้กฎสามข้อต่อไปนี้เพื่อใช้ฟังก์ชั่นและวิธีการอย่างถูกต้อง:
def
และตัวอักษรฟังก์ชั่นที่กำหนดโดย=>
เป็นฟังก์ชั่น มันถูกกำหนดไว้ในหน้า 143 บทที่ 8 ในหนังสือ Programming ใน Scala ฉบับที่ 4someNumber.foreach(println)
หลังจากการเขียนโปรแกรมสี่ฉบับใน Scala ยังคงเป็นปัญหาสำหรับผู้ใช้ที่จะแยกความแตกต่างของแนวคิดที่สำคัญสองประการ: ฟังก์ชันและค่าฟังก์ชันเนื่องจากทุกรุ่นไม่ได้ให้คำอธิบายที่ชัดเจน ข้อกำหนดภาษามีความซับซ้อนเกินไป ฉันพบว่ากฎข้างต้นนั้นเรียบง่ายและแม่นยำ