Introduction
Il s'agit d'une migration directe du didacticiel dwango, où vous allez l'étudier, le modifier et le remplacer par vos propres termes.
Bien que cela ne soit pas mentionné dans la section classe, une classe peut prendre zéro ou plusieurs types comme paramètres. Cela est utile lorsque vous souhaitez représenter un type que vous ne pouvez pas identifier au moment de la création d'une classe (par exemple, le type d'un élément dans une classe de collection). La syntaxe de la définition de classe avec les paramètres de type est la suivante.
class <nom de la classe>[<Paramètre de type 1>, <Paramètre de type 2>, ...](<Argumentdeclasse>) {
(<Définition du champ>|<Définition de la méthode>)*
}
Vous pouvez donner à chaque séquence de paramètres de type le nom de votre choix et l'utiliser dans la définition de classe. Dans le langage Scala, il est habituel de nommer A, B, ... depuis le début, il est donc sûr de le faire correspondre.
À titre d'exemple simple, définissons une classe Cell qui contient un élément et vous permet de placer (mettre) ou de récupérer (obtenir) l'élément. La définition de Cell est la suivante.
class Cell[A](varvalue:A) {
def put(newValue: A): Unit = {
value = newValue
}
def get(): A = value
}
Utilisons ceci dans 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")
^
Du code ci-dessus
scala> val cell = new Cell[Int](1)
cell: Cell[Int] = Cell@4bcc6842
Dans la partie de, le type Int est donné comme paramètre de type et 1 est donné comme valeur initiale. Depuis que j'ai instancié la cellule en donnant le paramètre de type Int, REPL a essayé de mettre une chaîne et elle a été rejetée par le compilateur comme une erreur. Puisque Cell est une classe que vous souhaitez instancier avec différents types, vous ne pouvez pas donner un type spécifique lors de la définition d'une classe. Les paramètres de type sont utiles dans de tels cas.
Ensuite, regardons un exemple plus pratique. Le désir de renvoyer plusieurs valeurs d'une méthode est courant en programmation. Pour les langues qui ne prennent pas en charge la langue (quelles langues telles que Scheme et Go ont) et les paramètres de type qui renvoient plusieurs valeurs
Renvoie une comme valeur de retour et l'autre via l'argument Créez une classe dédiée à plusieurs valeurs de retour à chaque fois que vous en avez besoin Il n'y avait que l'option. Cependant, la première est une mauvaise idée en ce qu'elle utilise des arguments comme valeurs de retour, et la dernière méthode est bonne si vous souhaitez renvoyer un grand nombre d'arguments ou si la classe a un nom significatif dans le problème à résoudre, mais seulement 2 Si vous souhaitez renvoyer deux valeurs, cela n'est pas pratique car le petit tour ne fonctionne pas. Dans ce cas, vous créeriez une classe Pair qui prend deux paramètres de type. La définition de la classe Pair est la suivante: Ne vous inquiétez pas, la définition de la méthode toString ne sera utilisée que pour un affichage ultérieur.
class Pair[A, B](vala:A,valb:B) {
override def toString(): String = "(" + a + "," + b + ")"
}
Un exemple d'utilisation de cette classe Pair est la méthode divide, qui renvoie à la fois le quotient et le reste de la division. La définition de la division est la suivante.
def divide(m: Int, n: Int): Pair[Int, Int] = new Pair[Int, Int](m/n,m%n)
Lorsque ceux-ci sont rassemblés dans REPL et versés, cela devient comme suit.
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)
Vous pouvez voir que le quotient de 7 divisé par 3 et le reste sont en res0. Dans ce cas, la nouvelle paire [Int, Int](m / n, m% n) est utilisée, mais elle peut être omise si le type du paramètre de type peut être déduit du type d'argument. Dans ce cas, les arguments donnés au constructeur de Pair sont Int et Int, donc new Pair (m / n, m% n) a la même signification. Cette paire peut être utilisée dans tous les cas où vous souhaitez renvoyer deux types différents (même le même type) comme valeur de retour. De cette manière, l'avantage des paramètres de type est que vous pouvez abstraire le cas où le même traitement est effectué pour tous les types.
À propos, puisque cette classe comme Pair est souvent utilisée dans Scala, Tuple1 à Tuple22 (le nombre après Tuple est le nombre d'éléments) sont préparés à l'avance. Aussi, lors de l'instanciation
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)
Même si tu ne le fais pas
scala> val m = 7
m: Int = 7
scala> val n = 3
n: Int = 3
scala> (m/n,m%n)
res2: (Int, Int) = (2,1)
C'est censé être bon.
Dans cette section, vous découvrirez les propriétés contravariantes et covariantes liées aux paramètres de type.
Dans Scala, les paramètres de type non spécifiés sont généralement invariants. L'invariant est uniquement lorsqu'il existe des classes G avec des paramètres de type, des paramètres de type A et B et A = B
val : G[A] = G[B]
Il montre la propriété qu'une telle substitution est autorisée. C'est une propriété naturelle étant donné que les classes ayant des paramètres de type différents seront de types différents. J'ose mentionner immuable ici parce que j'ai commis une erreur de conception selon laquelle les classes de tableau intégrées de Java sont covariantes plutôt qu'immuables par défaut.
Je n'ai pas encore mentionné la covariance, alors laissez-moi vous donner une brève définition. La covariation se produit uniquement lorsqu'il existe des classes G avec des paramètres de type, des paramètres de type A et B, et A hérite de B.
val : G[B] = G[A]
Il représente la propriété qu'une telle affectation est autorisée. Dans Scala, lors de la définition d'une classe
class G[+A]
Si vous ajoutez + avant un paramètre de type comme, le paramètre de type (ou sa classe) sera covariant.
Si cela reste tel quel, la définition peut être abstraite et difficile à comprendre, je vais donc expliquer en utilisant un type de tableau comme exemple concret. Un exemple intéressant est que les types de tableaux sont covariants en Java et invariants en Scala. Le premier est un exemple Java. Lire comme G = tableau, A = chaîne, B = objet.
Object[] objects = new String[1];
objects[0] = 100;
Ce fragment de code passe par la compilation en tant que code Java. À première vue, il semble raisonnable de pouvoir passer un tableau de chaînes à une variable qui représente un tableau d'objets. Cependant, lorsque j'exécute ce code, j'obtiens l'exception java.lang.ArrayStoreException. Il s'agit en fait d'un tableau de chaînes (qui n'a que des chaînes comme éléments) dans les objets, mais j'essaie de passer 100, qui est la valeur du type int (boxe convertie et type entier) dans la deuxième ligne. Ça dépend.
En revanche, dans Scala, lorsque vous essayez de compiler le code correspondant à la première ligne du même code, l'erreur de compilation suivante se produit (Any est une superclasse de tous types, AnyRef et AnyVal (type valeur) ) Les valeurs peuvent également être stockées).
scala> val arr: Array[Any] = new Array[String](1)
<console>:7: error: type mismatch;
found : Array[String]
required: Array[Any]
Cela est dû au fait que le tableau est immuable dans Scala. Si la sécurité de type d'un langage à typage statique doit attraper plus d'erreurs de programmation au moment de la compilation, alors Scala est plus sûr de type que Java dans la conception de tableaux.
Maintenant, dans Scala, lorsque vous covariez un paramètre de type, vous pouvez être assuré que le compilateur vous donnera une erreur pour les opérations non sécurisées, mais il est logique de savoir quand vous pouvez utiliser la covariation. .. Par exemple, considérez la classe Pair [A, B] que vous venez de créer. Une fois que Pair [A, B] est instancié, il ne peut pas être modifié, de sorte que des exceptions telles que ArrayStoreException ne peuvent pas se produire. En fait, Pair [A, B] est une classe qui peut être co-transformée en toute sécurité, et la classe Pair [+ A, + B] ne pose aucun problème.
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)
Ici, vous pouvez voir que Pair ne peut pas être modifié après avoir reçu une valeur au moment de la création, il n'y a donc pas de place pour une exception comme ArrayStoreException. En général, les paramètres de type tels que immuables une fois créés peuvent souvent être covariés.
Vient ensuite la contravariante, qui est une propriété qui est exactement associée à la covariante. Permettez-moi de vous donner une brève définition. L'inversion n'est possible que lorsqu'il existe des classes G avec des paramètres de type, des paramètres de type A et B et A hérite de B.
val : G[A] = G[B]
Il représente la propriété qu'une telle affectation est autorisée. Dans Scala, lors de la définition d'une classe
class G[-A]
Si vous préfixez un paramètre de type avec-, le paramètre de type (ou sa classe) sera contravariant.
L'un des exemples les plus évidents de contravariance est le type de fonction. Par exemple, lorsqu'il existe des types A et B
val x1: A => AnyRef = B =>Valeur de type AnyRef
x1(Valeur de type A)
A doit hériter de B pour que le fragment de programme réussisse. Le contraire n'est pas possible. Supposons que A = String et B = AnyRef.
val x1: String => AnyRef = AnyRef =>Valeur de type AnyRef
x1(Valeur du type de chaîne)
Ici, x1 contient en fait la valeur de type AnyRef => AnyRef, donc même si une valeur de type String est donnée en argument, cela revient à donner une valeur de type String à l'argument de type AnyRef. Cela réussira sans aucun problème. Si A et B sont inversés et A = AnyRef, B = String, ce sera la même chose que de donner une valeur de type AnyRef à l'argument de type String, donc cela entraînera une erreur de compilation lors de l'affectation d'une valeur à x1. Cela devrait l'être, et vous obtiendrez en fait une erreur de compilation.
Essayons-le avec 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>
De cette manière, le résultat est tel que décrit ci-dessus.
Si vous ne spécifiez rien pour le paramètre de type T, vous savez seulement que le paramètre de type T peut être de n'importe quel type. Par conséquent, la seule méthode qui peut être appelée pour le paramètre de type T qui ne spécifie rien est pour Any. Cependant, il peut être utile de pouvoir écrire des contraintes sur T, par exemple si vous souhaitez trier une liste d'éléments ordonnés. Les limites des paramètres de type peuvent être utilisées dans de tels cas. Il existe deux types de limites de paramètre de type.
La première concerne les limites supérieures, qui spécifient les types dont les paramètres de type héritent. Dans la limite supérieure, le paramètre type est suivi de <:, suivi du type de contrainte. Dans ce qui suit, après avoir défini la classe Show qui peut être convertie en chaîne par show, ShowablePair qui n'a que le type Show est défini comme un élément.
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 + ")"
}
Ici, Show est spécifié comme limite supérieure pour les deux paramètres de type A et B, donc show peut être appelé pour a et b. Si vous ne spécifiez pas explicitement la limite de limite supérieure, il est considéré que Any est spécifié.
La seconde est les limites inférieures, qui spécifient le type de supertype du paramètre de type. La borne inférieure est une caractéristique souvent utilisée avec les paramètres covariants. Voyons un exemple.
Tout d'abord, définissez une classe Stack immuable qui ressemblait à un exercice covariant. Supposons que vous souhaitiez que cette pile soit covariante.
abstract class Stack[+A]{
def push(element: A): Stack[A]
def top: A
def pop: Stack[A]
def isEmpty: Boolean
}
Cependant, cette définition entraîne une erreur de compilation similaire à la suivante:
error: covariant type A occurs in contravariant position in type A of value element
def push(element: A): Stack[A]
^
Cette erreur de compilation indique que le paramètre de type covariant A est apparu à une position contravariante (où le paramètre de type contravariant peut apparaître). En général, si la valeur du paramètre covariant E arrive à la position de l'argument, cette erreur se produira car la sécurité de type peut être rompue. Cependant, contrairement aux tableaux, cette pile est immuable, il ne devrait donc y avoir aucun problème de sécurité de type. Vous pouvez utiliser les limites inférieures des paramètres de type pour résoudre ce problème. Ajoutez le paramètre de type E pour pousser et spécifier le paramètre de type de pile A comme sa limite inférieure.
abstract class Stack[+A]{
def push[E >: A](element:E): Stack[E]
def top: A
def pop: Stack[A]
def isEmpty: Boolean
}
En faisant cela, le compilateur saura que la pile peut contenir des valeurs de n'importe quel supertype de A. Et comme le paramètre de type E n'est pas covariant, il peut apparaître n'importe où. De cette manière, la limite inférieure peut être utilisée pour obtenir à la fois une pile de type sûr et une covariation.
La prochaine fois, j'étudierai les fonctions.
Ce document est CC BY-NC-SA 3.0
Il est distribué sous.
https://dwango.github.io/scala_text/
Recommended Posts