51 votes

Scala: Comment définir des paramètres de fonction "génériques"?

Je suis en train d'apprendre Scala maintenant, avec un peu d'expérience en Haskell. Une chose qui était étrange pour moi, c'est que tous les paramètres de la fonction de Scala doit être annoté avec un type - quelque chose que Haskell n'a pas besoin. Pourquoi est-ce? Pour essayer de le mettre comme un exemple plus concret: une fonction d'ajout est écrit comme ceci:

def add(x:Double, y:Double) = x + y

Mais, cela ne fonctionne que pour les doubles(bien, ints trop de travail en raison de l'implicite de conversion de type). Mais que faire si vous souhaitez définir votre propre type qui définit ses propres + de l'opérateur. Comment voulez-vous écrire une fonction add qui fonctionne pour n'importe quel type qui définit un + opérateur?

68voto

Walter Chang Points 7041

Haskell utilise l'algorithme d'inférence de type Hindley-Milner alors que Scala, pour prendre en charge le côté objet des objets, devait renoncer à l'utiliser pour l'instant.

Pour écrire facilement une fonction d'ajout pour tous les types applicables, vous devez utiliser Scala 2.8.0:

 Welcome to Scala version 2.8.0.r18189-b20090702020221 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_15).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import Numeric._
import Numeric._

scala> def add[A](x: A, y: A)(implicit numeric: Numeric[A]): A = 
     | numeric.plus(x, y)
add: [A](x: A,y: A)(implicit numeric: Numeric[A])A

scala> add(1, 2)
res0: Int = 3

scala> add(1.1, 2.2)
res1: Double = 3.3000000000000003
 

18voto

airportyh Points 7912

Afin de consolider le concept de l'utilisation implicite pour moi-même, j'ai écrit un exemple qui ne nécessite pas de scala 2.8, mais utilise le même concept. J'ai pensé qu'il pourrait être utile pour certains. Tout d'abord, vous définissez un des génériques de la classe abstraite Addable:

scala> abstract class Addable[T]{
 |   def +(x: T, y: T): T
 | }
defined class Addable

Maintenant, vous pouvez écrire à l' ajout de la fonction comme ceci:

scala> def add[T](x: T, y: T)(implicit addy: Addable[T]): T = 
 | addy.+(x, y)
add: [T](T,T)(implicit Addable[T])T

Il est utilisé comme un type de classe en Haskell. Alors pour réaliser cette classe générique pour un type spécifique, vous devez écrire(des exemples ici pour Int, Double et String):

scala> implicit object IntAddable extends Addable[Int]{
 |   def +(x: Int, y: Int): Int = x + y
 | }
defined module IntAddable

scala> implicit object DoubleAddable extends Addable[Double]{
 |   def +(x: Double, y: Double): Double = x + y
 | }
defined module DoubleAddable

scala> implicit object StringAddable extends Addable[String]{
 |   def +(x: String, y: String): String = x concat y
 | }
defined module StringAddable

À ce stade, vous pouvez appeler l' ajout de la fonction avec les trois types:

scala> add(1,2)
res0: Int = 3

scala> add(1.0, 2.0)
res1: Double = 3.0

scala> add("abc", "def")
res2: java.lang.String = abcdef

Certainement pas aussi beau que Haskell qui va essentiellement faire tout cela pour vous. Mais, c'est là que le compromis se trouve.

3voto

Daniel C. Sobral Points 159554

Haskell utilise l'inférence de type Hindley-Milner . Ce type d'inférence de type est puissant, mais limite le système de type du langage. Soi-disant, par exemple, le sous-classement ne fonctionne pas bien avec HM.

Quoi qu'il en soit, le système de type Scala est trop puissant pour HM, il faut donc utiliser un type d'inférence de type plus limité.

3voto

fxt Points 36

Je pense que la raison Scala requiert le type d'annotation sur les paramètres d'une nouvelle fonction vient du fait que la Scala utilise plus locales inférence de type d'analyse que celle utilisée en Haskell.

Si toutes vos classes mixtes dans un trait, disons Addable[T], qui a déclaré l'opérateur+, vous pouvez écrire votre générique ajouter une fonction comme:

def ajout <: Addable[T] = x + y

Cette limite à la fonction d'ajout de types de T que de mettre en œuvre la Addable trait.

Malheureusement, il n'y a pas de trait dans le courant de la Scala bibliothèques. Mais vous pouvez voir comment cela pourrait être fait en regardant un cas similaire, la commande[T] trait de caractère. Ce trait de caractère déclare opérateurs de comparaison et est mélangé par le RichInt, RichFloat, etc. des classes. Ensuite, vous pouvez écrire une fonction de tri qui peut prendre, par exemple, une Liste[T] où [T <: Ordonné[T]] pour trier une liste d'éléments qui se mélangent dans la caractéristique. En raison de conversions de types implicites comme Flotter à RichFloat, vous pouvez même utiliser la fonction de tri sur les listes de type Int ou Float ou Double.

Comme je l'ai dit, malheureusement, n'est pas caractéristique de l'opérateur+. Donc, vous devez écrire tout ce que vous-même. Vous ne le Addable[T] trait de caractère, créer AddableInt, AddableFloat, etc., les classes qui étendent Int, Float, etc. et de le mélanger à la Addable trait, et enfin ajouter implicite des fonctions de conversion pour transformer, par exemple, et l'Int dans un AddableInt, de sorte que le compilateur peut instancier et utiliser votre fonction d'ajout avec elle.

1voto

mtnygard Points 4517

La fonction elle-même va être assez simple:

def add(x: T, y: T): T = ...

Mieux encore, il vous suffit de surcharger la méthode:

def +(x: T, y: T): T = ...

Il y a une pièce manquante, cependant, qui est le type de paramètre lui-même. Comme l'écrit, la méthode est absent de sa classe. Il est probable que vous appelez le + de la méthode sur une instance de T, de le transmettre à une autre instance de T. je l'ai fait récemment, la définition d'un trait de caractère qui a dit: "un groupe des additifs se compose d'une opération d'ajout plus les moyens d'inverser un élément"

trait GroupAdditive[G] extends Structure[G] {
  def +(that: G): G
  def unary_- : G
}

Puis, plus tard, j'définir une Véritable classe qui sait comment ajouter des instances de lui-même (le Champ s'étend GroupAdditive):

class Real private (s: LargeInteger, err: LargeInteger, exp: Int) extends Number[Real] with Field[Real] with Ordered[Real] {
  ...

  def +(that: Real): Real = { ... }

  ...
}

Qui peut être plus que vous avez vraiment envie de savoir maintenant, mais il montre à la fois comment définir les arguments génériques et de la façon de les réaliser.

En fin de compte, les types spécifiques ne sont pas nécessaires, mais le compilateur n'a besoin de connaître au moins le type de limites.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X