ประเภทของการใช้ความหมาย
นัยใน Scala หมายถึงค่าที่สามารถส่งผ่าน "อัตโนมัติ" เพื่อพูดหรือแปลงจากประเภทหนึ่งไปเป็นอีกประเภทหนึ่งที่สร้างขึ้นโดยอัตโนมัติ
การแปลงโดยนัย
พูดสั้น ๆ มากเกี่ยวกับประเภทหลังถ้าใครเรียกวิธีการmบนวัตถุoของชั้นCและชั้นที่ไม่สนับสนุนวิธีการmแล้ว Scala จะมองหาแปลงนัยจากCสิ่งที่ไม่mสนับสนุน ตัวอย่างง่ายๆจะเป็นวิธีการmapในString:
"abc".map(_.toInt)
Stringไม่รองรับวิธีการmapนี้ แต่StringOpsทำได้และมีการแปลงโดยนัยจากStringเป็นStringOpsใช้ได้ (ดูimplicit def augmentStringที่Predef)
พารามิเตอร์โดยปริยาย
ชนิดอื่น ๆ ของส่อเป็นนัยพารามิเตอร์ สิ่งเหล่านี้ถูกส่งผ่านไปยังการเรียกเมธอดเหมือนกับพารามิเตอร์อื่น ๆ แต่คอมไพเลอร์พยายามเติมให้เต็มโดยอัตโนมัติ หากไม่สามารถทำได้ก็จะบ่น หนึ่งสามารถส่งผ่านพารามิเตอร์เหล่านี้อย่างชัดเจนซึ่งเป็นวิธีการใช้breakOutเช่น (ดูคำถามเกี่ยวกับbreakOutในวันที่คุณรู้สึกถึงความท้าทาย)
ในกรณีนี้เราต้องประกาศความต้องการโดยนัยเช่นfooการประกาศวิธีการ:
def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
ดูขอบเขต
มีสถานการณ์หนึ่งที่ implicit เป็นทั้งการแปลงโดยนัยและพารามิเตอร์ implicit ตัวอย่างเช่น:
def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)
getIndex("abc", 'a')
วิธีการที่getIndexจะได้รับวัตถุใด ๆ Seq[T]ตราบใดที่มีการแปลงนัยใช้ได้จากชั้นเลิศ ด้วยเหตุนี้ฉันสามารถส่งผ่านStringไปยังgetIndexและมันจะทำงาน
เบื้องหลังคอมไพเลอร์ที่มีการเปลี่ยนแปลงไปseq.IndexOf(value)conv(seq).indexOf(value)
สิ่งนี้มีประโยชน์มากที่จะมีน้ำตาลซินแทคติคในการเขียน การใช้น้ำตาล syntactic นี้getIndexสามารถกำหนดเช่นนี้:
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
น้ำตาลซินแทคติคนี้มีคำอธิบายว่าเป็นมุมมองที่ถูกผูกไว้คล้ายกับขอบบน ( CC <: Seq[Int]) หรือขอบล่าง ( T >: Null)
ขอบเขตของบริบท
อีกรูปแบบที่พบบ่อยในพารามิเตอร์นัยเป็นรูปแบบประเภทระดับ รูปแบบนี้เปิดใช้งานการจัดเตรียมอินเทอร์เฟซทั่วไปให้กับคลาสที่ไม่ได้ประกาศ ทั้งสองอย่างสามารถทำหน้าที่เป็นรูปแบบบริดจ์ได้โดยแยกข้อกังวลออกและเป็นรูปแบบของอะแดปเตอร์
Integralระดับที่คุณกล่าวถึงเป็นตัวอย่างที่คลาสสิกของรูปแบบคลาสประเภท Orderingตัวอย่างเช่นในห้องสมุดมาตรฐานสกาล่าก็คือ มีห้องสมุดที่ใช้รูปแบบนี้อย่างหนักเรียกว่า Scalaz
นี่คือตัวอย่างการใช้งาน:
def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
นอกจากนี้ยังมีน้ำตาล syntactic สำหรับมันเรียกว่าบริบทที่ถูกผูกไว้ซึ่งทำให้มีประโยชน์น้อยลงโดยจำเป็นต้องอ้างถึงนัย การแปลงวิธีดังกล่าวจะมีลักษณะดังนี้:
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
ขอบเขตของบริบทมีประโยชน์มากขึ้นเมื่อคุณต้องการส่งผ่านไปยังวิธีอื่นที่ใช้งาน ตัวอย่างเช่นวิธีการsortedเกี่ยวกับความต้องการโดยปริยายSeq Orderingในการสร้างวิธีการreverseSortหนึ่งสามารถเขียน:
def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse
เพราะOrdering[T]ถูกส่งผ่านไปโดยปริยายreverseSortจึงสามารถส่งต่อไปโดยปริยายsortedได้
Implicits มาจากไหน
เมื่อคอมไพเลอร์เห็นความต้องการปริยายเนื่องจากคุณกำลังเรียกใช้เมธอดที่ไม่มีอยู่ในคลาสของวัตถุหรือเนื่องจากคุณกำลังเรียกใช้เมธอดที่ต้องการพารามิเตอร์ implicit มันจะค้นหา implicit ที่เหมาะสมกับความต้องการ .
การค้นหานี้ปฏิบัติตามกฎบางอย่างที่กำหนดว่า implicits ใดที่สามารถมองเห็นได้ ตารางต่อไปนี้แสดงให้เห็นว่าผู้รวบรวมจะค้นหา implicits จากการนำเสนอที่ยอดเยี่ยมเกี่ยวกับการ implicits โดย Josh Suereth ซึ่งฉันแนะนำให้ทุกคนที่ต้องการพัฒนาความรู้สกาล่าของพวกเขาอย่างเต็มที่ มันได้รับการเติมเต็มตั้งแต่นั้นมาพร้อมกับข้อเสนอแนะและการปรับปรุง
implicits ที่มีอยู่ภายใต้หมายเลข 1 ด้านล่างมีความสำคัญเหนือกว่าภายใต้หมายเลข 2 นอกเหนือจากนั้นหากมีข้อโต้แย้งที่มีสิทธิ์หลายอย่างซึ่งตรงกับประเภทของพารามิเตอร์ implicit หนึ่งที่เฉพาะเจาะจงที่สุดจะถูกเลือกโดยใช้กฎของการแก้ไขปัญหา ข้อมูลจำเพาะ§6.26.3) ข้อมูลรายละเอียดเพิ่มเติมสามารถพบได้ในคำถามที่ฉันลิงก์ไปที่ท้ายคำตอบนี้
- ดูครั้งแรกในขอบเขตปัจจุบัน
- นัยที่กำหนดไว้ในขอบเขตปัจจุบัน
- การนำเข้าที่ชัดเจน
- สัญลักษณ์ตัวแทนนำเข้า
ขอบเขตเดียวกันในไฟล์อื่น ๆ
- ตอนนี้ดูประเภทที่เกี่ยวข้องใน
- วัตถุร่วมประเภท
- ขอบเขตของประเภทอาร์กิวเมนต์โดยนัย(2.9.1)
- ขอบเขตโดยนัยของอาร์กิวเมนต์ชนิด(2.8.0)
- วัตถุภายนอกสำหรับชนิดซ้อนกัน
- มิติอื่น ๆ
ลองยกตัวอย่างสำหรับพวกเขา:
นัยที่กำหนดไว้ในขอบเขตปัจจุบัน
implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope
การนำเข้าที่ชัดเจน
import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM") // implicit conversion from Java Map to Scala Map
สัญลักษณ์ตัวแทนนำเข้า
def sum[T : Integral](list: List[T]): T = {
val integral = implicitly[Integral[T]]
import integral._ // get the implicits in question into scope
list.foldLeft(integral.zero)(_ + _)
}
ขอบเขตเดียวกันในไฟล์อื่น ๆ
แก้ไข : ดูเหมือนว่านี่จะไม่มีลำดับความสำคัญแตกต่างกัน หากคุณมีตัวอย่างที่แสดงให้เห็นถึงความแตกต่างที่มีความสำคัญโปรดแสดงความคิดเห็น มิฉะนั้นอย่าพึ่งพาสิ่งนี้
นี่เป็นเหมือนตัวอย่างแรก แต่สมมติว่าความหมายโดยนัยอยู่ในไฟล์ที่แตกต่างจากการใช้งาน ดูเพิ่มเติมว่าอาจใช้วัตถุแพ็กเกจในการนำ implicits อย่างไร
วัตถุร่วมของประเภท
มีโน้ตของวัตถุสองรายการที่นี่ ขั้นแรกให้มองหาคู่หูของวัตถุที่เป็น "แหล่งที่มา" ยกตัวอย่างเช่นภายในวัตถุOptionมีการแปลงส่อไปIterableดังนั้นหนึ่งสามารถเรียกIterableวิธีการในOptionหรือผ่านไปยังสิ่งที่คาดหวังว่าOption Iterableตัวอย่างเช่น:
for {
x <- List(1, 2, 3)
y <- Some('x')
} yield (x, y)
นิพจน์นั้นแปลโดยคอมไพเลอร์เป็น
List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))
อย่างไรก็ตามList.flatMapคาดว่า a TraversableOnceซึ่งOptionไม่ใช่ คอมไพเลอร์นั้นจะมองเข้าไปข้างในOptionวัตถุที่เป็นเพื่อนร่วมทางและพบว่าการแปลงเป็นIterableซึ่งTraversableOnceทำให้การแสดงออกนี้ถูกต้อง
ประการที่สองวัตถุร่วมของประเภทที่คาดหวัง:
List(1, 2, 3).sorted
วิธีการที่จะใช้เวลาโดยปริยายsorted Orderingในกรณีนี้จะมีลักษณะภายในวัตถุOrderingสหายในชั้นเรียนOrderingและพบนัยOrdering[Int]มี
โปรดสังเกตว่าวัตถุที่เป็นคู่หูของคลาสพิเศษนั้นจะถูกมองด้วย ตัวอย่างเช่น:
class A(val n: Int)
object A {
implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b // s == "A: 2"
นี่คือวิธีที่สกาล่าพบโดยปริยายNumeric[Int]และNumeric[Long]ในคำถามของคุณโดยวิธีการที่พวกเขาจะพบว่าข้างในไม่ได้NumericIntegral
ขอบเขตของประเภทอาร์กิวเมนต์โดยปริยาย
หากคุณมีเมธอดที่มีชนิดอาร์กิวเมนต์AขอบเขตของชนิดAนั้นจะถูกพิจารณาด้วย โดย "ขอบเขตโดยนัย" ฉันหมายความว่ากฎเหล่านี้ทั้งหมดจะถูกนำไปใช้ซ้ำ - ตัวอย่างเช่นวัตถุที่เป็นสหายของAจะถูกค้นหาโดยนัยตามกฎข้างต้น
โปรดทราบว่าสิ่งนี้ไม่ได้หมายความว่าขอบเขตโดยนัยของAจะถูกค้นหาการแปลงของพารามิเตอร์นั้น แต่เป็นการแสดงออกทั้งหมด ตัวอย่างเช่น:
class A(val n: Int) {
def +(other: A) = new A(n + other.n)
}
object A {
implicit def fromInt(n: Int) = new A(n)
}
// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)
มีให้ตั้งแต่ Scala 2.9.1
ขอบเขตของอาร์กิวเมนต์ชนิดโดยนัย
สิ่งนี้จะต้องทำให้รูปแบบคลาสของคลาสใช้งานได้จริง ลองพิจารณาOrderingตัวอย่าง: มันมาพร้อมกับนัยในวัตถุที่เป็นคู่หู แต่คุณไม่สามารถเพิ่มสิ่งต่าง ๆ ลงไปได้ ดังนั้นคุณจะสร้างOrderingชั้นเรียนของคุณเองโดยอัตโนมัติได้อย่างไร?
เริ่มจากการใช้งาน:
class A(val n: Int)
object A {
implicit val ord = new Ordering[A] {
def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
}
}
ดังนั้นให้พิจารณาสิ่งที่เกิดขึ้นเมื่อคุณโทร
List(new A(5), new A(2)).sorted
อย่างที่เราเห็นวิธีการsortedคาดหวังOrdering[A](จริง ๆ แล้วมันคาดหวังว่าOrdering[B]ที่ไหนB >: A) ไม่มีสิ่งใดอยู่ข้างในOrderingและไม่มีประเภท "แหล่งที่มา" ที่จะดู เห็นได้ชัดว่ามันคือการหามันไว้ภายในAซึ่งเป็นอาร์กิวเมนต์ชนิดOrderingของ
และนี่ก็เป็นวิธีการวิธีการเก็บรวบรวมคาดหวังต่างๆCanBuildFromทำงาน: implicits CanBuildFromที่พบภายในวัตถุสหายกับพารามิเตอร์ประเภทของ
หมายเหตุ : Orderingถูกกำหนดเป็นtrait Ordering[T]โดยที่Tพารามิเตอร์ประเภท ก่อนหน้านี้ฉันบอกว่า Scala ดูพารามิเตอร์ประเภทภายในซึ่งไม่สมเหตุสมผลนัก นัยมองหาข้างต้นเป็นOrdering[A]ที่Aเป็นประเภทที่เกิดขึ้นจริงไม่ได้พิมพ์พารามิเตอร์: มันเป็นอาร์กิวเมนต์ชนิดOrderingไป ดูหัวข้อ 7.2 ของข้อมูลจำเพาะสกาล่า
มีให้ตั้งแต่ Scala 2.8.0
วัตถุภายนอกสำหรับประเภทซ้อนกัน
ฉันไม่ได้เห็นตัวอย่างของสิ่งนี้จริงๆ ฉันจะขอบคุณถ้ามีใครสามารถแบ่งปัน หลักการง่าย ๆ :
class A(val n: Int) {
class B(val m: Int) { require(m < n) }
}
object A {
implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b // s == "B: 3"
มิติอื่น ๆ
ฉันค่อนข้างแน่ใจว่านี่เป็นเรื่องตลก แต่คำตอบนี้อาจไม่ทันสมัย ดังนั้นอย่าใช้คำถามนี้ว่าเป็นผู้ตัดสินคนสุดท้ายของสิ่งที่เกิดขึ้นและหากคุณสังเกตว่ามันล้าสมัยแล้วโปรดแจ้งให้ฉันทราบเพื่อที่ฉันจะได้แก้ไขได้
แก้ไข
คำถามที่น่าสนใจที่เกี่ยวข้อง: