ตัวบ่งชี้สกาล่าคืออะไร "โดยนัย"?


169

ฉันได้เห็นฟังก์ชั่นชื่อที่implicitlyใช้ในตัวอย่างสกาล่า มันคืออะไรและใช้อย่างไร?

ตัวอย่างที่นี่ :

scala> sealed trait Foo[T] { def apply(list : List[T]) : Unit }; object Foo {
     |                         implicit def stringImpl = new Foo[String] {
     |                             def apply(list : List[String]) = println("String")
     |                         }
     |                         implicit def intImpl = new Foo[Int] {
     |                             def apply(list : List[Int]) =  println("Int")
     |                         }
     |                     } ; def foo[A : Foo](x : List[A]) = implicitly[Foo[A]].apply(x)
defined trait Foo
defined module Foo
foo: [A](x: List[A])(implicit evidence$1: Foo[A])Unit

scala> foo(1)
<console>:8: error: type mismatch;
 found   : Int(1)
 required: List[?]
       foo(1)
           ^
scala> foo(List(1,2,3))
Int
scala> foo(List("a","b","c"))
String
scala> foo(List(1.0))
<console>:8: error: could not find implicit value for evidence parameter of type
 Foo[Double]
       foo(List(1.0))
          ^

โปรดทราบว่าเราต้องเขียนimplicitly[Foo[A]].apply(x)เนื่องจากคอมไพเลอร์คิดว่านั่น implicitly[Foo[A]](x)หมายความว่าเราเรียกimplicitlyพารามิเตอร์

ดูที่วิธีตรวจสอบวัตถุ / ประเภท / ฯลฯ จาก Scala REPL? และสกาล่ามองหาการปลูกฝังที่ไหน?

คำตอบ:


206

implicitlyที่นี่มีไม่กี่เหตุผลที่จะใช้วิธีการที่ง่ายครื้นเครง

เพื่อทำความเข้าใจ / แก้ไขปัญหามุมมองโดยนัย

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

scala> 1.min(2) // Int doesn't have min defined, where did that come from?                                   
res21: Int = 1

scala> implicitly[Int => { def min(i: Int): Any }]
res22: (Int) => AnyRef{def min(i: Int): Any} = <function1>

scala> res22(1) // 
res23: AnyRef{def min(i: Int): Int} = 1

scala> .getClass
res24: java.lang.Class[_] = class scala.runtime.RichInt

มุมมองโดยนัยยังสามารถถูกเรียกใช้เมื่อการแสดงออกไม่สอดคล้องกับประเภทที่คาดหวังดังต่อไปนี้:

scala> 1: scala.runtime.RichInt
res25: scala.runtime.RichInt = 1

ที่นี่คอมไพเลอร์มองหาฟังก์ชั่นนี้:

scala> implicitly[Int => scala.runtime.RichInt]
res26: (Int) => scala.runtime.RichInt = <function1>

การเข้าถึงพารามิเตอร์โดยปริยายที่ถูกผูกไว้กับบริบทที่แนะนำ

พารามิเตอร์โดยนัยเป็นคุณสมบัติที่สำคัญของ Scala มากกว่า Views Implicit พวกเขาสนับสนุนรูปแบบการเรียนประเภท ห้องสมุดมาตรฐานนี้ใช้ในสถานที่ไม่กี่ - ดูและวิธีการที่จะนำมาใช้ในscala.Ordering SeqLike#sortedพารามิเตอร์โดยนัยยังใช้เพื่อส่งรายการ Array และCanBuildFromอินสแตนซ์

Scala 2.8 อนุญาตให้มีการจดชวเลขไวยากรณ์สำหรับพารามิเตอร์โดยนัยที่เรียกว่าขอบเขตบริบท สั้น ๆ วิธีการที่มีพารามิเตอร์ประเภทAที่ต้องใช้พารามิเตอร์ประเภทM[A]:

def foo[A](implicit ma: M[A])

สามารถเขียนใหม่เป็น:

def foo[A: M]

แต่จุดผ่านพารามิเตอร์โดยนัยคืออะไร แต่ไม่ตั้งชื่อมัน สิ่งนี้จะมีประโยชน์เมื่อใช้งานวิธีการได้fooอย่างไร

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

def foo[A: M] = {
   val ma = implicitly[M[A]]
}

ผ่านชุดย่อยของพารามิเตอร์โดยนัยอย่างชัดเจน

สมมติว่าคุณกำลังเรียกเมธอดที่พิมพ์ชื่อคนสวย ๆ โดยใช้วิธีการพิมพ์ตามประเภทคลาส:

trait Show[T] { def show(t: T): String }
object Show {
  implicit def IntShow: Show[Int] = new Show[Int] { def show(i: Int) = i.toString }
  implicit def StringShow: Show[String] = new Show[String] { def show(s: String) = s }

  def ShoutyStringShow: Show[String] = new Show[String] { def show(s: String) = s.toUpperCase }
}

case class Person(name: String, age: Int)
object Person {
  implicit def PersonShow(implicit si: Show[Int], ss: Show[String]): Show[Person] = new Show[Person] {
    def show(p: Person) = "Person(name=" + ss.show(p.name) + ", age=" + si.show(p.age) + ")"
  }
}

val p = Person("bob", 25)
implicitly[Show[Person]].show(p)

ถ้าเราต้องการเปลี่ยนวิธีการที่ชื่อเป็นผลลัพธ์ เราอย่างชัดเจนสามารถเรียกPersonShowอย่างชัดเจนผ่านทางเลือกแต่เราต้องการคอมไพเลอร์ที่จะผ่านShow[String]Show[Int]

Person.PersonShow(si = implicitly, ss = Show.ShoutyStringShow).show(p)

2
scala> 1.min (2) res0: Int = 1 ใน Scala 2.10.3 ฉันได้รับข้อผิดพลาด: scala> โดยปริยาย [Int => {def min (i: Int): ใด ๆ }] <console>: 8: ข้อผิดพลาด: ไม่มีมุมมองโดยนัยจาก Int => AnyRef {def min (i: Int): Any} โดยนัย [Int => {def min (i: Int): Any}]
jhegedus

คำตอบนี้จะได้รับการอัพเดตสำหรับเวอร์ชั่นล่าสุด
emeth

1
โดยปริยาย [Int => AnyVal {def min (i: Int): Int}] จะใช้งานได้ ควรแก้ไขในคำตอบ
Malkaviano

212

Implicitlyสามารถใช้ได้ใน Scala 2.8 และกำหนดไว้ในPredefเป็น:

def implicitly[T](implicit e: T): T = e

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

ตัวอย่างง่าย ๆ จากการนำเสนอของ retronym :

scala> implicit val a = "test" // define an implicit value of type String
a: java.lang.String = test
scala> val b = implicitly[String] // search for an implicit value of type String and assign it to b
b: String = test
scala> val c = implicitly[Int] // search for an implicit value of type Int and assign it to c
<console>:6: error: could not find implicit value for parameter e: Int
       val c = implicitly[Int]
                         ^

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

17
implicitly[Ordering[(Int, String)]].compare( (1, "b"), (1, "a") )โดยเฉพาะอย่างยิ่งเพื่อดึงพารามิเตอร์ implicit ที่แนะนำโดย Context Bound:def foo[A: Ordering](a1: A, a2: A) = implicitly[Ordering[A]].compare(a1, a2)
retronym

1
หากต้องการดูการสนทนาของ retronym ในลิงก์วิดีโอด้านบนให้ข้ามไปที่ประเด็น 13:50
chaotic3quilibrium

-2

A "สอนให้คุณปลา" คำตอบคือการใช้ดัชนีสมาชิกตัวอักษรที่มีอยู่ในปัจจุบันnightlies Scaladoc ตัวอักษร (และ#, สำหรับชื่อที่ไม่ใช่ตัวอักษร) ที่ด้านบนของบานหน้าต่างแพคเกจ / คลาสคือลิงค์ไปยังดัชนีสำหรับชื่อสมาชิกที่ขึ้นต้นด้วยตัวอักษรนั้น (ในทุกคลาส) หากคุณเลือกIเช่นคุณจะพบimplicitlyรายการที่มีเหตุการณ์หนึ่งเกิดขึ้นPredefซึ่งคุณสามารถเยี่ยมชมได้จากลิงค์ที่นั่น


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


4
ประเภทลายเซ็นเอกสารนี้อันนี้ค่อนข้างดี
retronym

21
implicitดูเหมือนจะเป็นคุณลักษณะภาษาที่สำคัญใน Scala และควรค่าแก่การอธิบายอย่างเหมาะสม การคิดว่าเอกสารที่มีรายละเอียดเฉพาะการนับประเภทของลายเซ็นนั้นดูเหมือนว่าเป็นการทำให้ตนเองพึงพอใจทางปัญญามากกว่าคำตอบของแท้ ดูคำถามเฉพาะที่ถามโดย OP - มันคืออะไรและใช้อย่างไร? ไม่ตอบคำถามนี้หรือในเอกสารทุกคืนที่คุณไม่ได้ระบุลิงก์จริงให้ scala-lang.org/files/archive/nightly/docs/library/… สิ่งนี้ไม่ได้สอนอะไรเลย ดู Niklaus Wirth หรือ Turbo Pascal สำหรับตัวอย่างเอกสารแท้ -1
โทมัส W

3
implicitและimplicitlyมีความเกี่ยวข้อง แต่ค่อนข้างชัดเจน implicitคำหลักที่เป็นส่วนหนึ่งของภาษา implicitlyถูกกำหนดไว้ในรหัส Scala ธรรมดาในห้องสมุดมาตรฐาน เนื่องจากเอกสารออนไลน์มีลิงก์แหล่งข้อมูลฉันเชื่อว่ายังเป็นการดีที่สุดในการอ้างอิงผู้ถามถึงเอกสารเหล่านั้นและแหล่งข้อมูลที่เชื่อมโยง
Randall Schulz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.