[JAVA] Ich habe Scala ~ [Typparameter und Verschiebungsspezifikation] ~ berührt

Introduction

image.png

Es ist eine direkte Migration des Dwango-Tutorials, in dem Sie es studieren, bearbeiten und durch Ihre eigenen Begriffe ersetzen.

Parameter eingeben

Obwohl im Klassenabschnitt nicht erwähnt, kann eine Klasse null oder mehr Typen als Parameter annehmen. Dies ist nützlich, wenn Sie einen Typ darstellen möchten, den Sie zum Zeitpunkt der Erstellung einer Klasse nicht identifizieren können (z. B. den Typ eines Elements in einer Auflistungsklasse). Die Syntax der Klassendefinition mit den Typparametern lautet wie folgt.


class <Name der Klasse>[<Geben Sie Parameter 1 ein>, <Geben Sie Parameter 2 ein>, ...](<Klassenargument>) {
  (<Felddefinition>|<Methodendefinition>)*
}

Sie können jeder Typparametersequenz einen beliebigen Namen geben und ihn in der Klassendefinition verwenden. In der Scala-Sprache ist es üblich, A, B, ... von Anfang an zu benennen, daher ist es sicher, sie zuzuordnen.

Definieren wir als einfaches Beispiel eine Klasse Cell, die ein Element enthält und es Ihnen ermöglicht, das Element einzufügen (zu setzen) oder abzurufen (abzurufen). Die Definition von Zelle ist wie folgt.


class Cell[A](varvalue:A) {
  def put(newValue: A): Unit = {
    value = newValue
  }

  def get(): A = value
}

Verwenden wir dies in REPL.


scala> class Cell[A](varvalue:A) {
     |   def put(newValue: A): Unit = {
     |     value = newValue
     |   }
     |   
     |   def get(): A = value
     | }
defined class Cell

scala> val cell = new Cell[Int](1)
cell: Cell[Int] = Cell@192aaffb

scala> cell.put(2)

scala> cell.get()
res1: Int = 2

scala> cell.put("something")
<console>:10: error: type mismatch;
 found   : String("something")
 required: Int
              cell.put("something")
                       ^

Vom obigen Code


scala> val cell = new Cell[Int](1)
cell: Cell[Int] = Cell@4bcc6842

In dem Teil von wird der Typ Int als Typparameter und 1 als Anfangswert angegeben. Da ich die Zelle durch Angabe des Typparameters Int instanziiert habe, hat REPL versucht, einen String einzufügen, der vom Compiler als Fehler zurückgewiesen wurde. Da Cell eine Klasse ist, die Sie mit verschiedenen Typen instanziieren möchten, können Sie beim Definieren einer Klasse keinen bestimmten Typ angeben. Typparameter sind in solchen Fällen nützlich.

Schauen wir uns als nächstes ein praktischeres Beispiel an. Der Wunsch, mehrere Werte von einer Methode zurückzugeben, ist bei der Programmierung üblich. Für Sprachen ohne Sprachunterstützung (welche Sprachen wie Scheme und Go haben) und Typparameter, die mehrere Werte zurückgeben

Einer wird als Rückgabewert und der andere über das Argument zurückgegeben Erstellen Sie bei Bedarf eine Klasse für mehrere Rückgabewerte Es gab nur die Option. Ersteres ist jedoch insofern eine schlechte Idee, als es Argumente als Rückgabewert verwendet, und letzteres ist gut, wenn Sie eine große Anzahl von Argumenten zurückgeben möchten oder wenn die Klasse in dem zu lösenden Problem einen aussagekräftigen Namen hat, aber nur 2 Wenn Sie zwei Werte zurückgeben möchten, ist dies unpraktisch, da die kleine Kurve nicht funktioniert. In diesem Fall würden Sie eine Pair-Klasse erstellen, die zwei Typparameter akzeptiert. Die Definition der Pair-Klasse lautet wie folgt: Keine Sorge, die Definition der toString-Methode wird erst später zur Anzeige verwendet.


class Pair[A, B](vala:A,valb:B) {
  override def toString(): String = "(" + a + "," + b + ")"
}

Ein Beispiel für die Verwendung dieses Klassenpaars ist die Methodenteilung, die sowohl den Quotienten als auch den Rest der Teilung zurückgibt. Die Definition von Teilen ist wie folgt.


def divide(m: Int, n: Int): Pair[Int, Int] = new Pair[Int, Int](m/n,m%n)

Wenn diese in REPL zusammengesetzt und gegossen werden, wird es wie folgt.


scala> class Pair[A, B](vala:A,valb:B) {
     |   override def toString(): String = "(" + a + "," + b + ")"
     | }
defined class Pair

scala> def divide(m: Int, n: Int): Pair[Int, Int] = new Pair[Int, Int](m/n,m%n)
divide: (m: Int, n: Int)Pair[Int,Int]

scala> divide(7, 3)
res0: Pair[Int,Int] = (2,1)

Sie können sehen, dass der Quotient von 7 geteilt durch 3 und der Rest in res0 sind. In diesem Fall wird das neue Paar [Int, Int](m / n, m% n) verwendet, das jedoch weggelassen werden kann, wenn der Typ des Typparameters aus dem Argumenttyp abgeleitet werden kann. In diesem Fall lauten die Argumente, die dem Konstruktor des Paares gegeben werden, Int und Int, sodass das neue Paar (m / n, m% n) dieselbe Bedeutung hat. Dieses Paar kann in allen Fällen verwendet werden, in denen Sie zwei verschiedene Typen (sogar denselben Typ) als Rückgabewert zurückgeben möchten. Auf diese Weise besteht der Vorteil von Typparametern darin, dass Sie den Fall abstrahieren können, in dem für alle Typen dieselbe Verarbeitung ausgeführt wird.

Übrigens, da diese Klasse wie Pair in Scala häufig verwendet wird, werden Tuple1 bis Tuple22 (die Zahl nach Tuple ist die Anzahl der Elemente) im Voraus vorbereitet. Auch beim Instanziieren


scala> val m = 7
m: Int = 7

scala> val n = 3
n: Int = 3

scala> new Tuple2(m/n,m%n)
res1: (Int, Int) = (2,1)

Auch wenn du es nicht tust


scala> val m = 7
m: Int = 7

scala> val n = 3
n: Int = 3

scala> (m/n,m%n)
res2: (Int, Int) = (2,1)

Es soll gut sein.

Verschiebungsspezifikation (Varianz)

In diesem Abschnitt erfahren Sie mehr über kontravariante und kovariante Eigenschaften in Bezug auf Typparameter.

Covariant

In Scala sind nicht angegebene Typparameter normalerweise unveränderlich. Invariante ist nur, wenn es Klassen G mit Typparametern, Typparametern A und B und A = B gibt


val : G[A] = G[B]

Es zeigt die Eigenschaft, dass eine solche Substitution zulässig ist. Dies ist eine natürliche Eigenschaft, wenn man bedenkt, dass Klassen mit unterschiedlichen Typparametern unterschiedliche Typen haben. Ich wage es, hier unveränderlich zu erwähnen, weil ich einen Designfehler gemacht habe, dass Javas integrierte Array-Klassen eher kovariant als standardmäßig unveränderlich sind.

Ich habe die Kovarianz noch nicht erwähnt, daher möchte ich Ihnen eine kurze Definition geben. Kovariation ist nur möglich, wenn es Klassen G mit Typparametern, Typparametern A und B gibt und A B erbt.


val : G[B] = G[A]

Es stellt die Eigenschaft dar, dass eine solche Zuordnung zulässig ist. In Scala beim Definieren einer Klasse


class G[+A]

Wenn Sie + vor einem Typparameter wie hinzufügen, ist der Typparameter (oder seine Klasse) kovariant.

Wenn dies so bleibt, kann die Definition abstrakt und schwer zu verstehen sein, daher werde ich die Verwendung eines Array-Typs als konkretes Beispiel erläutern. Ein interessantes Beispiel ist, dass Array-Typen in Java kovariant und in Scala invariant sind. Erstens ist ein Java-Beispiel. Lesen Sie als G = Array, A = String, B = Objekt.


Object[] objects = new String[1];
objects[0] = 100;

Dieses Codefragment wird als Java-Code kompiliert. Auf den ersten Blick erscheint es sinnvoll, ein Array von Strings an eine Variable übergeben zu können, die ein Array von Objekten darstellt. Wenn ich diesen Code ausführe, erhalte ich jedoch die Ausnahme java.lang.ArrayStoreException. Dies ist eigentlich ein Array von Strings (mit nur Strings als Elementen) in Objekten, aber ich versuche, 100 zu übergeben, was der Wert des Typs int (Boxing konvertiert und Integer-Typ) in der zweiten Zeile ist. Es hängt davon ab, ob.

Wenn ich in Scala hingegen versuche, den Code zu kompilieren, der der ersten Zeile ähnlichen Codes entspricht, wird der folgende Kompilierungsfehler angezeigt (Any ist eine Oberklasse aller Typen, AnyRef und AnyVal (Werttyp)). ) Werte können auch gespeichert werden).


scala> val arr: Array[Any] = new Array[String](1)
<console>:7: error: type mismatch;
 found   : Array[String]
 required: Array[Any]

Dies liegt daran, dass das Array in Scala unveränderlich ist. Wenn statisch typisierte Sprachtypensicherheit beim Kompilieren mehr Programmierfehler abfangen soll, ist Scala im Array-Design typsicherer als Java.

Wenn Sie in Scala einen Typparameter kovariieren, können Sie sicher sein, dass der Compiler Ihnen einen Fehler für unsichere Operationen ausgibt. Es ist jedoch sinnvoll zu wissen, wann Sie die Kovariation verwenden können. .. Betrachten Sie beispielsweise das soeben erstellte Klassenpaar [A, B]. Sobald das Paar [A, B] instanziiert ist, kann es nicht mehr geändert werden, sodass Ausnahmen wie ArrayStoreException nicht auftreten können. Tatsächlich ist Paar [A, B] eine Klasse, die sicher co-transformiert werden kann, und Klassenpaar [+ A, + B] verursacht keine Probleme.


scala> class Pair[+A, +B](vala:A,valb:B) {
     |   override def toString(): String = "(" + a + "," + b + ")"
     | }
defined class Pair

scala> val pair: Pair[AnyRef, AnyRef] = new Pair[String, String]("foo","bar")
pair: Pair[AnyRef,AnyRef] = (foo,bar)

Hier sehen Sie, dass das Paar nicht geändert werden kann, nachdem es beim Erstellen einen Wert erhalten hat. Daher ist kein Platz für eine Ausnahme wie ArrayStoreException. Im Allgemeinen können Typparameter wie unveränderliche einmal erstellte Daten häufig kovariiert werden.

Gegenvariante

Als nächstes kommt die Kontravariante, die genau die Eigenschaft ist, die mit der Kovariante gepaart ist. Lassen Sie mich eine kurze Definition geben. Die Inversion erfolgt nur, wenn Klassen G mit Typparametern, Typparametern A und B vorhanden sind und A B erbt.


val : G[A] = G[B]
Es stellt die Eigenschaft dar, dass eine solche Zuordnung zulässig ist. In Scala beim Definieren einer Klasse

class G[-A]

Wenn Sie einem Typparameter vor- stellen, z. B. wird der Typparameter (oder seine Klasse) kontravariant.

Eines der offensichtlichsten Beispiele für Kontravarianz ist die Art der Funktion. Zum Beispiel, wenn es Typen A und B gibt


val x1: A => AnyRef = B =>AnyRef-Typwert
x1(Geben Sie einen Wert ein)

A muss von B erben, damit das Programmfragment erfolgreich ist. Das Gegenteil ist nicht möglich. Nehmen wir an, dass A = String und B = AnyRef.


val x1: String => AnyRef = AnyRef =>AnyRef-Typwert
x1(Zeichenfolgentypwert)

Hier enthält x1 tatsächlich den Wert vom Typ AnyRef => AnyRef. Selbst wenn ein Wert vom Typ String als Argument angegeben wird, entspricht dies der Angabe eines Werts vom Typ String vom Typ AnyRef. Es wird ohne Probleme gelingen. Wenn A und B umgekehrt sind und A = AnyRef, B = String, entspricht dies der Angabe eines AnyRef-Typwerts für das String-Typ-Argument. Dies führt zu einem Kompilierungsfehler, wenn x1 ein Wert zugewiesen wird. Sollte sein, und Sie erhalten tatsächlich einen Kompilierungsfehler.

Versuchen wir es tatsächlich mit REPL.


scala> val x1: AnyRef => AnyRef = (x: String) => (x:AnyRef)
<console>:7: error: type mismatch;
 found   : String => AnyRef
 required: AnyRef => AnyRef
       val x1: AnyRef => AnyRef = (x: String) => (x:AnyRef)
                                              ^

scala> val x1: String => AnyRef = (x: AnyRef) => x
x1: String => AnyRef = <function1>

Auf diese Weise ist das Ergebnis wie oben beschrieben.

Geben Sie Parametergrenzen ein

Wenn Sie für den Typparameter T nichts angeben, wissen Sie nur, dass der Typparameter T von einem beliebigen Typ sein kann. Daher ist die einzige Methode, die für den Typparameter T aufgerufen werden kann, der nichts angibt, Any. Es kann jedoch nützlich sein, Einschränkungen für T schreiben zu können, beispielsweise wenn Sie eine Liste geordneter Elemente sortieren möchten. In solchen Fällen können Typparametergrenzen verwendet werden. Es gibt zwei Arten von Typparametergrenzen.

Obergrenzen

Die erste ist die Obergrenze, die angibt, welche Typen die Typparameter erben. In der oberen Schranke folgt auf den Typparameter <:, gefolgt vom Einschränkungstyp. Im Folgenden wird ShowablePair, das nur den Typ Show hat, als Element definiert, nachdem die Klasse Show definiert wurde, die von show in eine Zeichenfolge konvertiert werden kann.


abstract class Show {
  def show: String
}
class ShowablePair[A <: Show, B <: Show](vala:A,valb:B) extends Show {
  override def show: String = "(" + a.show + "," + b.show + ")"
}

Hier wird Show als Obergrenze für beide Typparameter A und B angegeben, sodass show für a und b aufgerufen werden kann. Wenn Sie die Obergrenze nicht explizit angeben, wird davon ausgegangen, dass Any angegeben ist.

Untergrenzen

Die zweite ist die Untergrenze, die angibt, welcher Supertyp der Typparameter ist. Die Untergrenze ist ein Merkmal, das häufig bei kovarianten Parametern verwendet wird. Sehen wir uns ein Beispiel an.

Definieren Sie zunächst eine unveränderliche Stapelklasse, die einer kovarianten Übung gleicht. Angenommen, Sie möchten, dass dieser Stapel kovariant ist.


abstract class Stack[+A]{
  def push(element: A): Stack[A]
  def top: A
  def pop: Stack[A]
  def isEmpty: Boolean
}

Diese Definition führt jedoch zu einem Kompilierungsfehler ähnlich dem folgenden:


error: covariant type A occurs in contravariant position in type A of value element
         def push(element: A): Stack[A]
                           ^

Dieser Kompilierungsfehler besagt, dass der Parameter A des kovarianten Typs an einer kontravarianten Position angezeigt wurde (wo der Parameter des kontravarianten Typs erscheinen kann). Wenn der Wert des kovarianten Parameters E an die Position des Arguments kommt, tritt dieser Fehler im Allgemeinen auf, weil die Typensicherheit beeinträchtigt werden kann. Im Gegensatz zu Arrays ist dieser Stapel jedoch unveränderlich, sodass keine Sicherheitsprobleme auftreten sollten. Sie können die unteren Grenzen der Typparameter verwenden, um dieses Problem zu beheben. Fügen Sie den Typparameter E hinzu, um den Stapeltypparameter A als Untergrenze zu drücken und anzugeben.


abstract class Stack[+A]{
  def push[E >: A](element:E): Stack[E]
  def top: A
  def pop: Stack[A]
  def isEmpty: Boolean
}

Auf diese Weise weiß der Compiler, dass der Stack Werte eines beliebigen Supertyps von A enthalten kann. Und da der Typparameter E nicht kovariant ist, kann er überall auftreten. Auf diese Weise kann die Untergrenze verwendet werden, um sowohl einen typsicheren Stapel als auch eine Co-Modifikation zu erreichen.

Ende

Nächstes Mal werde ich Funktionen studieren.

Referenz

Dieses Dokument ist CC BY-NC-SA 3.0

image.png Es wird unter verteilt.

https://dwango.github.io/scala_text/

Recommended Posts

Ich habe Scala ~ [Typparameter und Verschiebungsspezifikation] ~ berührt
Ich habe Scala berührt
Ich habe Scala ~ [Klasse] ~ berührt
Ich habe Scala ~ [Objekt] ~ berührt
Ich habe Scala ~ [Trate] ~ berührt
Ich habe Scala ~ [Steuerungssyntax] ~ berührt
Wie sollten Firebase Analytics-Ereignisse und -Parameter festgelegt werden?