Apa artinya <: <, <% <, dan =: = dalam Scala 2.8, dan di mana mereka didokumentasikan?

201

Saya dapat melihat di dokumen API untuk Predef bahwa mereka adalah subkelas dari tipe fungsi umum (Dari) => Ke, tetapi hanya itu yang dikatakan. Um, apa? Mungkin ada dokumentasi di suatu tempat, tetapi mesin pencari tidak menangani "nama" seperti "<: <" sangat baik, jadi saya belum dapat menemukannya.

Pertanyaan tindak lanjut: kapan saya harus menggunakan simbol / kelas yang funky ini, dan mengapa?

Jeff
sumber
6
Berikut adalah pertanyaan terkait yang dapat menjawab pertanyaan Anda setidaknya sebagian: stackoverflow.com/questions/2603003/operator-in-scala
Yardena
13
symbolhound.com adalah teman pencarian kode Anda :)
ron
Apakah Haskell typeclassmelakukan pekerjaan operator ini? Contoh: compare :: Ord a => a -> a -> Ordering? Saya mencoba untuk memahami konsep Scala ini sehubungan dengan mitra Haskell-nya.
Kevin Meredith

Jawaban:

217

Ini disebut batasan tipe umum . Mereka memungkinkan Anda, dari dalam kelas tipe-parameter atau sifat, untuk lebih jauh membatasi salah satu parameter jenisnya. Ini sebuah contoh:

case class Foo[A](a:A) { // 'A' can be substituted with any type
    // getStringLength can only be used if this is a Foo[String]
    def getStringLength(implicit evidence: A =:= String) = a.length
}

Argumen implisit evidencedisediakan oleh kompiler, iff Aadalah String. Anda dapat menganggap itu sebagai bukti bahwa Aadalah Stringargumen --the itu sendiri tidak penting, hanya mengetahui bahwa itu ada. [edit: baiklah, secara teknis itu sebenarnya penting karena itu merupakan konversi implisit dari Ake String, yang memungkinkan Anda menelepon a.lengthdan tidak meminta kompiler berteriak kepada Anda]

Sekarang saya bisa menggunakannya seperti ini:

scala> Foo("blah").getStringLength
res6: Int = 4

Tetapi jika saya mencoba menggunakannya dengan Foosesuatu yang mengandung selain String:

scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]

Anda dapat membaca kesalahan itu sebagai "tidak dapat menemukan bukti bahwa Int == String" ... itu sudah seharusnya! getStringLengthmemaksakan pembatasan lebih lanjut pada jenis dari Aapa yang Foosecara umum mengharuskan; yaitu, Anda hanya dapat meminta getStringLengthpada a Foo[String]. Batasan ini diberlakukan pada waktu kompilasi, yang keren!

<:<dan <%<bekerja serupa, tetapi dengan sedikit variasi:

  • A =:= B berarti A harus persis B
  • A <:< Bberarti A harus merupakan subtipe B (analog dengan batasan tipe sederhana<: )
  • A <%< Bberarti A harus dapat dilihat sebagai B, mungkin melalui konversi implisit (analog dengan batasan tipe sederhana <%)

Cuplikan oleh @retronym ini adalah penjelasan yang bagus tentang bagaimana hal seperti ini dapat diselesaikan dan bagaimana batasan tipe umum membuatnya lebih mudah sekarang.

TAMBAHAN

Untuk menjawab pertanyaan tindak lanjut Anda, diakui contoh yang saya berikan cukup dibuat-buat dan tidak jelas berguna. Tapi bayangkan menggunakannya untuk mendefinisikan sesuatu seperti List.sumIntsmetode, yang menambahkan daftar bilangan bulat. Anda tidak ingin mengizinkan metode ini dipanggil pada yang lama List, hanya a List[Int]. Namun Listkonstruktor tipe tidak dapat dibatasi; Anda masih ingin dapat memiliki daftar string, foo, bar, dan apa pun. Jadi dengan menempatkan batasan tipe umum sumInts, Anda dapat memastikan bahwa hanya metode yang memiliki kendala tambahan yang hanya dapat digunakan pada a List[Int]. Pada dasarnya Anda sedang menulis kode kasus khusus untuk beberapa jenis daftar.

Tom Crockett
sumber
3
Oke, tapi ada juga metode dengan nama yang sama Manifest, yang tidak Anda sebutkan.
Daniel C. Sobral
3
Metode pada Manifestyang <:<dan >:>hanya ... sejak OP disebutkan persis 3 varietas jenis kendala umum, saya mengasumsikan bahwa apa yang dia tertarik.
Tom Crockett
12
@IttayD: cukup pintar ... class =:=[From, To] extends From => To, yang berarti bahwa nilai tipe From =:= Toimplisit sebenarnya merupakan konversi implisit dari Frommenjadi To. Jadi dengan menerima parameter implisit dari tipe yang A =:= StringAnda katakan yang Adapat secara implisit dikonversi String. Jika Anda mengubah urutan dan membuat argumen implisit menjadi tipe String =:= A, itu tidak akan berhasil, karena ini akan menjadi konversi implisit dari Stringmenjadi A.
Tom Crockett
25
Apakah simbol tiga karakter itu memiliki nama? Masalah saya dengan sup simbol Scala adalah bahwa mereka sulit untuk dibicarakan secara verbal, dan praktis tidak mungkin untuk menggunakan Google atau mesin pencari lainnya untuk menemukan diskusi dan contoh penggunaannya.
Gigatron
4
@ Andrea Tidak, ini hanya akan berfungsi jika tipenya sama persis. Perhatikan bahwa saya mengatakan bahwa memiliki nilai implisit dari jenis From =:= Tolingkup menyiratkan bahwa Anda memiliki konversi implisit From => To, tetapi implikasinya tidak berjalan mundur; memiliki konversi implisit A => Btidak tidak berarti Anda memiliki sebuah instance dari A =:= B. =:=adalah kelas abstrak tersegel yang didefinisikan dalam scala.Predef, dan hanya memiliki satu contoh terbuka untuk umum, yang tersirat, dan bertipe A =:= A. Jadi, Anda dijamin bahwa nilai implisit tipe A =:= Bsaksi fakta Adan Bsama.
Tom Crockett
55

Bukan jawaban yang lengkap (orang lain sudah menjawab ini), saya hanya ingin mencatat yang berikut, yang mungkin membantu untuk memahami sintaks lebih baik: Cara Anda biasanya menggunakan "operator" ini, seperti misalnya dalam contoh pelotom:

def getStringLength(implicit evidence: A =:= String)

memanfaatkan alternatif Scala sintaks infix untuk operator tipe .

Jadi, A =:= Stringsama dengan =:=[A, String](dan =:=hanya kelas atau sifat dengan nama yang tampak mewah). Perhatikan bahwa sintaks ini juga berfungsi dengan kelas "reguler", misalnya Anda dapat menulis:

val a: Tuple2[Int, String] = (1, "one")

seperti ini:

val a: Int Tuple2 String = (1, "one")

Ini mirip dengan dua sintaks untuk panggilan metode, "normal" dengan .dan ()dan sintaksis operator.

Jesper
sumber
2
perlu disuarakan karena makes use of Scala's alternative infix syntax for type operators.sama sekali kehilangan penjelasan ini yang tanpanya semuanya tidak masuk akal
Ovidiu Dolha
39

Baca jawaban lain untuk memahami apa itu konstruksi. Inilah saatnya Anda harus menggunakannya. Anda menggunakannya ketika Anda perlu membatasi metode untuk tipe tertentu saja.

Berikut ini sebuah contoh. Misalkan Anda ingin mendefinisikan Pasangan yang homogen, seperti ini:

class Pair[T](val first: T, val second: T)

Sekarang Anda ingin menambahkan metode smaller, seperti ini:

def smaller = if (first < second) first else second

Itu hanya berfungsi jika Tdipesan. Anda dapat membatasi seluruh kelas:

class Pair[T <: Ordered[T]](val first: T, val second: T)

Tapi itu memalukan - mungkin ada manfaat untuk kelas saat Ttidak dipesan. Dengan batasan tipe, Anda masih bisa mendefinisikan smallermetode:

def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second

Tidak masalah untuk instantiate, katakanlah, a Pair[File], selama Anda tidak menelepon smaller .

Dalam kasus ini Option, para pelaksana menginginkan suatu orNullmetode, meskipun itu tidak masuk akal Option[Int]. Dengan menggunakan batasan tipe, semuanya baik-baik saja. Anda dapat menggunakan orNullpada Option[String], dan Anda dapat membentuk Option[Int]dan menggunakannya, selama Anda tidak memanggilnya orNull. Jika Anda mencoba Some(42).orNull, Anda mendapatkan pesan yang menarik

 error: Cannot prove that Null <:< Int
cayhorstmann
sumber
2
Saya menyadari bahwa ini bertahun-tahun setelah jawaban ini, tetapi saya mencari kasus penggunaan <:<, dan saya pikir Orderedcontohnya tidak begitu menarik lagi karena sekarang Anda lebih suka menggunakan Orderingtypeclass daripada Orderedsifat. Sesuatu seperti: def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second.
ebruchez
1
@ebruchez: kasus penggunaan adalah untuk pengkodean tipe serikat dalam skala yang tidak dimodifikasi, lihat milessabin.com/blog/2011/06/09/scala-union-types-curry-howard
17

Itu tergantung di mana mereka digunakan. Paling sering, ketika digunakan saat mendeklarasikan tipe parameter implisit, mereka adalah kelas. Mereka juga bisa menjadi objek dalam kasus yang jarang terjadi. Akhirnya, mereka bisa menjadi operator pada Manifestobjek. Mereka didefinisikan di dalamscala.Predef dalam dalam dua kasus pertama, meskipun tidak didokumentasikan dengan baik.

Mereka dimaksudkan untuk menyediakan cara untuk menguji hubungan antara kelas, seperti <:dan <%lakukan, dalam situasi ketika yang terakhir tidak dapat digunakan.

Adapun pertanyaan "kapan saya harus menggunakannya?", Jawabannya adalah Anda seharusnya tidak, kecuali Anda tahu Anda harus melakukannya. :-) EDIT : Ok, ok, berikut adalah beberapa contoh dari perpustakaan. Pada Either, Anda memiliki:

/**
  * Joins an <code>Either</code> through <code>Right</code>.
  */
 def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match {
   case Left(a)  => Left(a)
   case Right(b) => b
 }

 /**
  * Joins an <code>Either</code> through <code>Left</code>.
  */
 def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
   case Left(a)  => a
   case Right(b) => Right(b)
 }

Pada Option, Anda memiliki:

def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null

Anda akan menemukan beberapa contoh lain di koleksi.

Daniel C. Sobral
sumber
Apakah ini :-)salah satunya? Dan saya akan setuju bahwa jawaban Anda untuk "Kapan saya harus menggunakannya?" berlaku untuk banyak hal.
Mike Miller
"Mereka dimaksudkan untuk menyediakan cara untuk menguji hubungan antara kelas-kelas" <- terlalu umum untuk membantu
Jeff
3
"Adapun pertanyaan" kapan saya harus menggunakannya? ", Jawabannya adalah Anda seharusnya tidak, kecuali Anda tahu Anda harus melakukannya." <- Karena itulah aku bertanya. Saya ingin bisa membuat tekad untuk saya sendiri.
Jeff