บริบทของสกาล่าคืออะไรและดูขอบเขตอย่างไร


267

ในวิธีง่าย ๆ บริบทและมุมมองขอบเขตคืออะไรและอะไรคือความแตกต่างระหว่างพวกเขา

ตัวอย่างที่ง่ายต่อการติดตามจะดีเช่นกัน!

คำตอบ:


477

ฉันคิดว่าคำถามนี้ถูกถามไปแล้ว แต่ถ้าเป็นเช่นนั้นคำถามจะไม่ปรากฏในแถบ "ที่เกี่ยวข้อง" ดังนั้นนี่คือ:

View Bound คืออะไร

มุมมองที่ถูกผูกไว้เป็นกลไกที่นำมาใช้ในการเปิดใช้งานกาลาใช้ชนิดบางA ราวกับว่าBมันเป็นบางชนิด ไวยากรณ์ทั่วไปคือ:

def f[A <% B](a: A) = a.bMethod

ในคำอื่น ๆAควรจะมีการแปลงโดยปริยายที่จะBสามารถใช้งานได้เพื่อให้เราสามารถเรียกวิธีการเกี่ยวกับวัตถุของการพิมพ์B Aการใช้ขอบเขตการดูโดยทั่วไปในไลบรารีมาตรฐาน (ก่อนหน้าสกาล่า 2.8.0 อยู่ดี) มีOrderedดังนี้:

def f[A <% Ordered[A]](a: A, b: A) = if (a < b) a else b

เพราะสามารถแปลงAเป็นOrdered[A]และเพราะOrdered[A]กำหนดวิธีการที่จะสามารถใช้การแสดงออก<(other: A): Booleana < b

โปรดทราบว่าขอบเขตการดูไม่ได้รับการสนับสนุนคุณควรหลีกเลี่ยง

บริบทที่ถูกผูกไว้คืออะไร?

ขอบเขตบริบทได้รับการแนะนำใน Scala 2.8.0 และโดยทั่วไปจะใช้กับรูปแบบคลาสของชนิดที่เรียกว่าเป็นรูปแบบของโค้ดที่เลียนแบบฟังก์ชันการทำงานที่จัดทำโดยคลาสประเภท Haskell แม้ว่าในลักษณะที่ละเอียดมากขึ้น

ในขณะที่มุมมองที่ถูกผูกไว้สามารถใช้กับประเภทที่เรียบง่าย (ตัวอย่างเช่นA <% String) บริบทที่ถูกผูกไว้ต้องใช้ประเภทพารามิเตอร์เช่นOrdered[A]ด้านบน แต่ไม่เหมือนStringกัน

บริบทผูกพันอธิบายนัยค่าแทนของมุมมองผูกพันของนัยแปลง มันถูกใช้เพื่อประกาศว่าสำหรับบางประเภทAมีค่าโดยนัยของประเภทที่B[A]มีอยู่ ไวยากรณ์เป็นดังนี้:

def f[A : B](a: A) = g(a) // where g requires an implicit value of type B[A]

นี่คือความสับสนมากกว่ามุมมองที่ถูกผูกไว้เพราะมันไม่ชัดเจนว่าจะใช้งานได้อย่างไร ตัวอย่างทั่วไปของการใช้งานใน Scala คือ:

def f[A : ClassManifest](n: Int) = new Array[A](n)

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

อีกตัวอย่างที่พบบ่อยมากในไลบรารีนั้นซับซ้อนกว่าเล็กน้อย:

def f[A : Ordering](a: A, b: A) = implicitly[Ordering[A]].compare(a, b)

ที่นี่implicitlyจะใช้ในการดึงค่าปริยายเราต้องการเป็นหนึ่งในประเภทซึ่งระดับกำหนดวิธีการOrdering[A]compare(a: A, b: A): Int

เราจะเห็นอีกวิธีหนึ่งในการทำสิ่งนี้ด้านล่าง

ขอบเขตการดูและขอบเขตของบริบทมีการใช้งานอย่างไร

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

def f[A <% B](a: A) = a.bMethod
def f[A](a: A)(implicit ev: A => B) = a.bMethod

def g[A : B](a: A) = h(a)
def g[A](a: A)(implicit ev: B[A]) = h(a)

ดังนั้นโดยธรรมชาติเราสามารถเขียนพวกเขาในรูปแบบเต็มซึ่งเป็นประโยชน์สำหรับขอบเขตบริบท:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = ord.compare(a, b)

ขอบเขตการดูใช้ทำอะไร

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

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

def f[A <% Ordered[A]](a: A, b: A): A = if (a < b) a else b

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

def f[A](a: Ordered[A], b: A): Boolean = a < b

การแปลงที่นี่ (ถ้าจำเป็น) เกิดขึ้นก่อนที่ฉันจะส่งพารามิเตอร์ไปfให้ดังนั้นfไม่จำเป็นต้องรู้

นอกจากนี้Orderedการใช้งานที่พบบ่อยที่สุดจากไลบรารีคือการจัดการStringและArrayซึ่งเป็นคลาส Java เช่นเดียวกับคอลเลกชัน Scala ตัวอย่างเช่น:

def f[CC <% Traversable[_]](a: CC, b: CC): CC = if (a.size < b.size) a else b

หากหนึ่งในความพยายามที่จะทำเช่นนี้โดยมุมมองขอบเขตประเภทการกลับมาของStringจะเป็นWrappedString(Scala 2.8) Arrayและในทำนองเดียวกันสำหรับ

สิ่งเดียวกันจะเกิดขึ้นแม้ว่าประเภทจะใช้เป็นพารามิเตอร์ประเภทของประเภทส่งคืนเท่านั้น:

def f[A <% Ordered[A]](xs: A*): Seq[A] = xs.toSeq.sorted

ขอบเขตของบริบทใช้ทำอะไร

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

ตัวอย่างคลาสสิกคือ Scala 2.8's Orderingซึ่งแทนที่Orderedตลอดทั้งห้องสมุดของ Scala การใช้งานคือ:

def f[A : Ordering](a: A, b: A) = if (implicitly[Ordering[A]].lt(a, b)) a else b

แม้ว่าโดยปกติคุณจะเห็นว่าเขียนแบบนี้:

def f[A](a: A, b: A)(implicit ord: Ordering[A]) = {
    import ord.mkOrderingOps
    if (a < b) a else b
}

ซึ่งใช้ประโยชน์จากการแปลงโดยนัยบางอย่างภายในOrderingที่เปิดใช้งานรูปแบบโอเปอเรเตอร์แบบดั้งเดิม อีกตัวอย่างหนึ่งใน Scala 2.8 คือNumeric:

def f[A : Numeric](a: A, b: A) = implicitly[Numeric[A]].plus(a, b)

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

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

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

แก้ไข

คำถามที่เกี่ยวข้องที่น่าสนใจ:


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

3
@chrsan ฉันได้เพิ่มอีกสองส่วนเพื่อดูรายละเอียดเพิ่มเติมว่าจะใช้แต่ละส่วนได้อย่างไร
Daniel C. Sobral

2
ฉันคิดว่านี่เป็นคำอธิบายที่ยอดเยี่ยม ฉันต้องการแปลสิ่งนี้สำหรับบล็อกภาษาเยอรมันของฉัน (dgronau.wordpress.com) ถ้ามันไม่เป็นไรกับคุณ
Landei

3
นี่คือคำอธิบายที่ดีที่สุดและครอบคลุมที่สุดของหัวข้อนี้ที่ฉันได้ค้นพบแล้ว ขอบคุณมากจริงๆ!
fotNelton

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