wake-up-neo.com

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

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

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

192
Jeff

Diese werden als generalisierte Typeinschränkungen bezeichnet. Sie ermöglichen es Ihnen, innerhalb einer typparametrisierten Klasse oder Eigenschaft einen ihrer Typparameter zu weiter einzugrenzen. 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 evidence wird vom Compiler geliefert, wenn AString ist. Sie können sich das als Beweis vorstellen, dass AString ist - das Argument selbst ist nicht wichtig, nur weil Sie wissen, dass es existiert. [edit: naja, technisch gesehen ist es wichtig, weil es eine implizite Konvertierung von A in String darstellt, mit der Sie a.length aufrufen können und nicht soll der Compiler dich anschreien]

Jetzt kann ich es so benutzen:

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

Aber wenn ich versucht habe, es mit einem Foo zu verwenden, das etwas anderes als ein String enthält:

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

Sie können diesen Fehler als "konnte keinen Beweis für Int == String finden" lesen ... das ist so, wie es sein sollte! getStringLength legt weitere Einschränkungen für den Typ von A fest, als das, was Foo im Allgemeinen erfordert; Sie können nämlich nur getStringLength für einen Foo[String] aufrufen. Diese Einschränkung wird zur Kompilierungszeit erzwungen, was cool ist!

<:< Und <%< Funktionieren ähnlich, jedoch mit geringfügigen Abweichungen:

  • A =:= B Bedeutet, dass A genau B sein muss
  • A <:< B Bedeutet, dass A ein Subtyp von B sein muss (analog zur simple Typ-Einschränkung <:)
  • A <%< B Bedeutet, dass A einsehbar sein muss als B, möglicherweise über implizite Konvertierung (analog zur einfachen Typbedingung <%)

Dieses Snippet von @retronym ist eine gute Erklärung dafür, wie so etwas früher ausgeführt wurde und wie es jetzt durch verallgemeinerte Typbeschränkungen einfacher wird.

[~ # ~] Nachtrag [~ # ~]

Um Ihre Anschlussfrage zu beantworten, ist das Beispiel, das ich gegeben habe, zugegebenermaßen ziemlich ausgeklügelt und offensichtlich nicht nützlich. Stellen Sie sich vor, Sie definieren damit so etwas wie eine List.sumInts - Methode, die eine Liste von ganzen Zahlen zusammenfasst. Sie möchten nicht zulassen, dass diese Methode für ein altes List aufgerufen wird, nur für ein List[Int]. Der Konstruktor vom Typ List kann jedoch nicht so eingeschränkt werden. Sie möchten immer noch in der Lage sein, Listen mit Strings, Foos, Bars und Dingsbums zu haben. Wenn Sie also sumInts eine allgemeine Typeinschränkung zuweisen, können Sie sicherstellen, dass nur diese Methode eine zusätzliche Einschränkung hat, die nur für List[Int] Verwendet werden kann. Im Wesentlichen schreiben Sie Sonderfallcode für bestimmte Arten von Listen.

203
Tom Crockett

Keine vollständige Antwort (andere haben bereits darauf geantwortet), ich wollte nur Folgendes beachten, um die Syntax besser zu verstehen: So, wie Sie diese "Operatoren" normalerweise verwenden, wie zum Beispiel im Beispiel von pelotom:

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

nutzt Scalas Alternative Infix-Syntax für Typoperatoren .

So, A =:= String ist das gleiche wie =:=[A, String] (und =:= ist nur eine Klasse oder Eigenschaft mit einem ausgefallenen Namen. Beachten Sie, dass diese Syntax auch mit "regulären" Klassen funktioniert. Sie können beispielsweise 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 die Operatorsyntax.

53
Jesper

Lesen Sie die anderen Antworten, um zu verstehen, was diese Konstrukte sind. Hier ist wann Sie sollten sie verwenden. 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)

Nun möchten Sie eine Methode smaller wie folgt hinzufügen:

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

Das funktioniert nur, wenn T bestellt 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 Verwendungen für die Klasse geben, wenn T nicht bestellt wird. Mit einer Typeinschränkung können Sie weiterhin die smaller -Methode definieren:

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

Es ist in Ordnung, beispielsweise einen Pair[File] zu instanziieren, solange Sie nicht smaller aufrufen.

Im Fall von Option wollten die Implementierer eine orNull -Methode, obwohl dies für Option[Int] Keinen Sinn ergibt. Wenn Sie eine Typeinschränkung verwenden, ist alles in Ordnung. Sie können orNull für einen Option[String] Verwenden und Sie können einen Option[Int] Bilden und verwenden, solange Sie nicht orNull für diesen aufrufen. Wenn Sie Some(42).orNull versuchen, erhalten Sie die charmante Nachricht

 error: Cannot prove that Null <:< Int
38
cayhorstmann

Es kommt darauf an, wo sie eingesetzt werden. In den meisten Fällen handelt es sich bei der Deklaration impliziter Parameter um Klassen. In seltenen Fällen können sie auch Objekte sein. Schließlich können sie Operatoren für Manifest Objekte sein. Sie sind in den ersten beiden Fällen in scala.Predef Definiert, jedoch nicht besonders gut dokumentiert.

Sie sollen eine Möglichkeit bieten, die Beziehung zwischen den Klassen zu testen, genau wie es <: Und <% Tun, wenn letztere nicht verwendet werden können.

Was die Frage "Wann soll ich sie verwenden?" Betrifft, lautet die Antwort, dass Sie es nicht tun sollten, es sei denn, Sie wissen, dass Sie es sollten. :-) EDIT : Ok, ok, hier sind einige Beispiele aus der Bibliothek. Auf Either haben Sie:

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

Auf Option haben Sie:

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

Weitere Beispiele finden Sie in den Kollektionen.

17

In Scala 2.13 wurden sie aus Predef verschoben: Move <: <, =: =, DummyImplicits out of Predef # 7350

Eine Funktion von Typbeschränkungen, die in anderen Antworten möglicherweise nicht explizit angegeben wurden, ist, dass sie verwendet werden können

... beschränken jeden abstrakten Typ T, der im Geltungsbereich ist in der Argumentliste einer Methode ( nicht nur die eigenen Typparameter der Methode )

Hier ist ein Beispiel, das den "nicht nur die eigenen Typparameter der Methode" zeigt Aspekt. Sagen wir haben

case class Foo[A, B](f: A => B) {
  def bar[C <: A](x: C)(implicit e: B <:< String): B = f(x)
}

Foo[Int, String](x => x.toString).bar(1) // OK.
Foo[Int, Double](x => x.toDouble).bar(1) // error: Cannot prove that Double <:< String.

Beachten Sie, wie wir den Typparameter B einschränken können, obwohl er nicht in der Typparameter-Klausel [C <: A] Von bar enthalten ist. Wenn wir stattdessen versucht haben, B in der Typparameter-Klausel von bar wie folgt einzuschränken

def bar[B <: String]

wir würden den Typparameter B aus dem einschließenden Bereich von Foo[A, B] abschatten. Ein reales Beispiel hierfür aus der Bibliothek wäre toMap :

trait IterableOnceOps[+A, +CC[_], +C] extends Any {
  ...
  def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] =
  ...
}
0
Mario Galic