Scala: comment définir les paramètres de fonction "génériques"?

j'essaie d'apprendre le Scala maintenant, avec un peu d'expérience à Haskell. Une chose qui s'est distinguée comme étrange pour moi est que tous les paramètres de fonction dans Scala doit être annoté avec un type - quelque chose que Haskell ne nécessite pas. 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(Eh bien, les ints fonctionnent aussi en raison du type implicite conversion.) Mais que faire si vous voulez définir votre propre type qui définit son propre opérateur + . Comment écririez-vous une fonction add qui fonctionne pour n'importe quel type qui définit un opérateur + ?

47
demandé sur Dan Burton 2009-08-10 06:42:31

5 réponses

Haskell utilise L'algorithme D'inférence de type Hindley-Milner alors que Scala, afin de supporter le côté orienté objet des choses, a dû renoncer à l'utiliser pour le moment.

pour écrire facilement une fonction add pour tous les types applicables, vous devrez 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
68
répondu Walter Chang 2009-08-10 03:40:12

afin de solidifier le concept d'utilisation de implicite pour moi, j'ai écrit un exemple qui ne nécessite pas scala 2.8, mais utilise le même concept. J'ai pensé qu'il pourrait être utile pour certains. Premièrement, vous définissez une classe générique-abstraite Addable :

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

Maintenant, vous pouvez écrire le ajouter 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 une classe de type dans Haskell. Puis à réaliser cette classe générique pour un type spécifique, vous écririez (exemples ici pour Int, Double et chaîne):

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 point, vous pouvez appeler le ajouter 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 bien que Haskell qui fera essentiellement tout cela pour vous. Mais c'est là que réside le compromis.

18
répondu airportyh 2009-08-11 16:02:03

Haskell utilise l'inférence de type Hindley-Milner . Ce type d'inférence de type est puissant, mais limite le type du système de la langue. Il semble, par exemple, que le sous-classement ne fonctionne pas bien avec H-M.

en tout cas, le système de type Scala est trop puissant pour H-M, donc une inférence de type plus limitée doit être utilisée.

3
répondu Daniel C. Sobral 2017-05-23 11:54:37

je pense que la raison pour laquelle Scala nécessite l'annotation de type sur les paramètres d'une fonction nouvellement définie vient du fait que Scala utilise une analyse d'inférence de type plus locale que celle utilisée dans Haskell.

si toutes vos classes mélangées dans un trait, dites Addable[T] , qui a déclaré l'opérateur + , vous pouvez écrire votre fonction add générique comme:

def add[T <: Addable[T]](x : T, y : T) = x + y

cela restreint la fonction add aux types T qui implémentent la Trait Addable.

malheureusement, il n'y a pas de tel trait dans les bibliothèques Scala actuelles. Mais vous pouvez voir comment il serait fait en regardant un cas similaire, le trait Ordered[T] . Ce trait déclare les opérateurs de comparaison et est mélangé par le RichInt , RichFloat , etc. classe. Ensuite, vous pouvez écrire une fonction de tri qui peut prendre, par exemple, un List[T][T <: Ordered[T]] pour trier une liste d'éléments qui se mélangent dans la caractéristique. En raison de type implicite conversions comme Float en RichFloat , vous pouvez même utiliser votre fonction de tri sur les listes de Int , ou Float ou Double .

comme je l'ai dit, malheureusement, il n'y a pas de trait correspondant pour l'opérateur + . Donc, tu devrais tout écrire toi-même. Vous feriez Le trait[T] Addable, créeriez AddableInt , AddableFloat , etc., les classes qui étendent Int, Float, etc. et mélanger dans le trait Addable, et finalement ajouter implicite fonctions de conversion pour tourner, par exemple , et Int dans un AddableInt , de sorte que le compilateur peut instancier et utiliser votre fonction add avec elle.

3
répondu fxt 2016-09-06 12:16:46

la fonction elle-même sera assez simple:

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

mieux encore, vous pouvez simplement surcharger la méthode+:

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

il y a une pièce manquante, qui est le paramètre de type lui-même. Comme écrit, la méthode manque sa classe. Le cas le plus probable est que vous appelez la méthode + sur une instance de T, lui passant une autre instance de T. j'ai fait cela récemment, définissant un trait qui disait, "un groupe additif se compose d'une opération add plus le moyen d'inverser un élément "

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

ensuite, plus tard, je définirai une classe réelle qui sait comment ajouter des instances d'elle-même (le champ étend GroupAdditive):

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

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

  ...
}

c'est peut-être plus que ce que vous vouliez savoir en ce moment, mais ça montre à la fois comment définir des arguments génériques et comment les réaliser.

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

1
répondu mtnygard 2009-08-10 03:31:48