Was bedeuten <: <, <% <und =: = in Scala 2.8 und wo sind sie dokumentiert?

201

Ich kann in den API-Dokumenten für Predef sehen, dass es sich um Unterklassen eines generischen Funktionstyps (From) => To handelt, aber das ist alles, was darin steht. Ähm, was? Vielleicht gibt es irgendwo Dokumentation, aber Suchmaschinen verarbeiten "Namen" wie "<: <" nicht sehr gut, so dass ich sie nicht finden konnte.

Folgefrage: Wann sollte ich diese funky Symbole / Klassen verwenden und warum?

Jeff
quelle
6
Hier ist eine verwandte Frage, die Ihre Frage zumindest teilweise beantworten kann: stackoverflow.com/questions/2603003/operator-in-scala
Yardena
13
symbolhound.com ist Ihr Code-Such-Freund :)
Ron
Führen Haskells typeclasses die Arbeit dieser Bediener aus? Beispiel : compare :: Ord a => a -> a -> Ordering? Ich versuche, dieses Scala-Konzept in Bezug auf sein Haskell-Gegenstück zu verstehen.
Kevin Meredith

Antworten:

217

Diese werden als allgemeine Typeinschränkungen bezeichnet . Sie ermöglichen es Ihnen, innerhalb einer typparametrisierten Klasse oder Eigenschaft eine ihrer Typparameter weiter einzuschränken . Hier ist ein Beispiel:

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
}

Das implizite Argument evidencewird vom Compiler bereitgestellt, iff Aist String. Sie können als daran denken Beweis , dass Aist String--die Argument selbst ist nicht wichtig, nur zu wissen , dass es existiert. [edit: Nun, technisch gesehen ist es tatsächlich wichtig, weil es eine implizite Konvertierung von Anach darstellt String, was es dir ermöglicht, aufzurufen a.lengthund den Compiler dich nicht anschreien zu lassen]

Jetzt kann ich es so benutzen:

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

Aber wenn ich es mit einem Fooanderen als einem String:

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

Sie können diesen Fehler lesen als "konnte keinen Beweis dafür finden, dass Int == String" ... das ist, wie es sein sollte! getStringLengthlegt weitere Beschränkungen für die Art fest, Aals dies Fooim Allgemeinen erforderlich ist; Sie können nämlich nur getStringLengthauf a aufrufen Foo[String]. Diese Einschränkung wird zur Kompilierungszeit erzwungen, was cool ist!

<:<und <%<arbeiten ähnlich, aber mit geringfügigen Abweichungen:

  • A =:= B bedeutet, dass A genau B sein muss
  • A <:< Bbedeutet, dass A ein Subtyp von B sein muss (analog zur einfachen Typbeschränkung <:)
  • A <%< Bbedeutet, dass A als B angezeigt werden muss , möglicherweise durch implizite Konvertierung (analog zur einfachen Typbeschränkung <%).

Dieses Snippet von @retronym ist eine gute Erklärung dafür, wie so etwas früher erreicht wurde und wie allgemeine Typeinschränkungen es jetzt einfacher machen.

NACHTRAG

Um Ihre Folgefrage zu beantworten, ist das Beispiel, das ich gegeben habe, zugegebenermaßen ziemlich erfunden und offensichtlich nicht nützlich. Stellen Sie sich vor, Sie definieren damit eine List.sumIntsMethode, die eine Liste von Ganzzahlen zusammensetzt. Sie möchten nicht zulassen, dass diese Methode für eine alte Methode aufgerufen wird, sondern Listnur für eine List[Int]. Der ListTypkonstruktor kann jedoch nicht so eingeschränkt werden. Sie möchten weiterhin Listen mit Zeichenfolgen, Foos, Balken und Dingsbums haben können. Wenn Sie also eine allgemeine Typeinschränkung festlegen sumInts, können Sie sicherstellen, dass nur diese Methode eine zusätzliche Einschränkung aufweist, die nur für a verwendet werden kann List[Int]. Im Wesentlichen schreiben Sie Sonderfallcode für bestimmte Arten von Listen.

Tom Crockett
quelle
3
Na gut, aber es gibt auch gleichnamige Methoden Manifest, die Sie nicht erwähnt haben.
Daniel C. Sobral
3
Die Methoden Manifestsind <:<und >:>nur ... da OP genau die 3 Arten von allgemeinen Typbeschränkungen erwähnte, gehe ich davon aus, dass er daran interessiert war.
Tom Crockett
12
@IttayD: Es ist ziemlich clever ... class =:=[From, To] extends From => To, was bedeutet, dass ein impliziter Wert vom Typ From =:= Totatsächlich eine implizite Konvertierung von Fromnach ist To. Wenn Sie also einen impliziten Parameter vom Typ akzeptieren, A =:= Stringsagen Sie, dass Adieser implizit in konvertiert werden kann String. Wenn Sie die Reihenfolge ändern und das implizite Argument vom Typ String =:= Amachen würden, würde es nicht funktionieren, da dies eine implizite Konvertierung von Stringnach wäre A.
Tom Crockett
25
Haben diese dreistelligen Symbole Namen? Mein Problem mit Scalas Symbolsuppe ist, dass es schwierig ist, mündlich darüber zu sprechen, und es praktisch unmöglich ist, Google oder eine andere Suchmaschine zu verwenden, um Diskussionen und Beispiele für ihre Verwendung zu finden.
Gigatron
4
@Andrea Nein, dies funktioniert nur, wenn die Typen genau gleich sind. Beachten Sie, dass ich sagte, dass ein impliziter Wert vom Typ From =:= Toim Bereich impliziert, dass Sie eine implizite Konvertierung haben From => To, aber die Implikation läuft nicht rückwärts. Eine implizite Konvertierung A => Bbedeutet nicht, dass Sie eine Instanz von haben A =:= B. =:=ist eine versiegelte abstrakte Klasse, die in definiert scala.Predefist und nur eine öffentlich exponierte Instanz hat, die implizit und vom Typ ist A =:= A. Sie sind also garantiert, dass ein impliziter Wert des Typs A =:= Bdie Tatsache bezeugt, dass Aund Bgleich sind.
Tom Crockett
55

Keine vollständige Antwort (andere haben dies bereits beantwortet), ich wollte nur Folgendes beachten, was möglicherweise zum besseren Verständnis der Syntax beiträgt: Die Art und Weise, wie Sie diese "Operatoren" normalerweise verwenden, wie zum Beispiel im Beispiel von pelotom:

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

nutzt die alternative Infix-Syntax von Scala für Typoperatoren .

Also, A =:= Stringist das gleiche wie =:=[A, String](und =:=ist nur eine Klasse oder ein Merkmal mit einem ausgefallenen Namen). Beachten Sie, dass diese Syntax auch mit "regulären" Klassen funktioniert. Sie können beispielsweise Folgendes schreiben:

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

so was:

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

Es ähnelt den beiden Syntaxen für Methodenaufrufe, der "normalen" mit .und ()und der Operatorsyntax.

Jesper
quelle
2
braucht Upvote, weil makes use of Scala's alternative infix syntax for type operators.diese Erklärung, ohne die das Ganze keinen Sinn ergibt, völlig fehlt
Ovidiu Dolha
39

Lesen Sie die anderen Antworten, um zu verstehen, was diese Konstrukte sind. Hier ist, wann Sie sie verwenden sollten. Sie verwenden sie, wenn Sie eine Methode nur für bestimmte Typen einschränken müssen.

Hier ist ein Beispiel. Angenommen, Sie möchten ein homogenes Paar wie folgt definieren:

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

Jetzt möchten Sie eine Methode smallerwie die folgende hinzufügen :

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

Das funktioniert nur, wenn Tbestellt wird. Sie können die gesamte Klasse einschränken:

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

Aber das scheint eine Schande zu sein - es könnte Verwendungszwecke für die Klasse geben, wenn sie Tnicht bestellt wird. Mit einer Typbeschränkung können Sie die smallerMethode weiterhin definieren :

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

Es ist in Ordnung zu instanziieren, sagen wir, ein Pair[File], solange Sie nicht nennen smaller darauf.

Im Fall von Optionwollten die Implementierer eine orNullMethode, obwohl dies für nicht sinnvoll ist Option[Int]. Durch die Verwendung einer Typeinschränkung ist alles in Ordnung. Sie können orNullein verwenden Option[String], und Sie können ein bilden Option[Int]und verwenden, solange Sie es nicht aufrufen orNull. Wenn Sie es versuchen Some(42).orNull, erhalten Sie die charmante Nachricht

 error: Cannot prove that Null <:< Int
Cayhorstmann
quelle
2
Mir ist klar, dass dies Jahre nach dieser Antwort liegt, aber ich suche nach Anwendungsfällen für <:<und ich denke, dass das OrderedBeispiel nicht mehr so ​​überzeugend ist, da Sie jetzt lieber die OrderingTypklasse als das OrderedMerkmal verwenden würden. So etwas wie : def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second.
Ebruchez
1
@ebruchez: Ein Anwendungsfall ist für die Codierung von Unionstypen
17

Es hängt davon ab, wo sie verwendet werden. Wenn sie beim Deklarieren impliziter Parametertypen verwendet werden, handelt es sich meistens um Klassen. In seltenen Fällen können sie auch Objekte sein. Schließlich können sie Operatoren für ManifestObjekte sein. Sie sind scala.Predefin den ersten beiden Fällen im Inneren definiert , jedoch nicht besonders gut dokumentiert.

Sie sollen einen Weg , um die Beziehung zwischen den Klassen zu testen, wie <:und <%zu tun, in Situationen , wenn diese nicht verwendet werden kann.

Die Frage "Wann soll ich sie verwenden?" Lautet, sollten Sie dies nicht tun, es sei denn, Sie wissen, dass Sie es sollten. :-) EDIT : Ok, ok, hier sind einige Beispiele aus der Bibliothek. Ein Either, Sie haben:

/**
  * 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)
 }

Ein Option, Sie haben:

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

Weitere Beispiele finden Sie in den Sammlungen.

Daniel C. Sobral
quelle
Ist :-)noch einer davon? Und ich würde zustimmen, dass Ihre Antwort auf "Wann sollte ich sie verwenden?" gilt für sehr viele Dinge.
Mike Miller
"Sie sollen eine Möglichkeit bieten, die Beziehung zwischen den Klassen zu testen" <- zu allgemein, um hilfreich zu sein
Jeff
3
"Was die Frage betrifft" Wann soll ich sie verwenden? ", Lautet die Antwort, dass Sie dies nicht tun sollten, es sei denn, Sie wissen, dass Sie es sollten." <- Deshalb frage ich. Ich möchte diese Entscheidung für mich selbst treffen können.
Jeff