Question

Je suis en train d'apprendre Scala maintenant, avec un peu d'expérience dans Haskell. Une chose qui était comme étrange pour moi est que tous les paramètres de la fonction Scala doivent être annotés avec un type - quelque chose qui 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 (bien, ints travaillent aussi à cause de la conversion de type implicite). Mais si vous voulez définir votre propre type qui définit son propre + opérateur. Comment voulez-vous écrire une fonction d'ajout qui fonctionne pour tout type qui définit une + opérateur?

Était-ce utile?

La solution

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

Pour écrire une fonction add pour tous les types applicables facilement, 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

Autres conseils

Afin de renforcer le concept d'utiliser implicite pour moi-même, j'ai écrit un exemple qui ne nécessite pas scala 2.8, mais utilise le même concept. Je pensais que ce serait peut-être utile pour certains. Tout d'abord, vous définissez une classe générique abstraite ajoutable :

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

Maintenant, vous pouvez écrire 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 realize cette classe générique pour un type spécifique, vous pouvez écrire (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 le ajouter fonction avec 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 sera essentiellement faire tout cela pour vous. Mais, c'est là les mensonges des compromis.

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

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

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

Ceci limite la fonction d'ajout à des types T qui mettent en œuvre le trait ajoutable.

Malheureusement, il n'y a pas un 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ée par le RichInt, RichFloat, etc. classes. 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 le trait commandé. En raison des conversions implicites de type comme Float à 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 trait correspondant à l'opérateur de +. Donc, vous devez écrire tout ce que vous-même. Vous feriez le ajoutable [T] trait, créer AddableInt, AddableFloat, etc., des classes qui étendent Int, Float, etc. et se mélangent dans le trait ajoutable, et enfin ajouter des fonctions de conversion implicites à tourner, par exemple, et dans un Int AddableInt, de sorte que le compilateur peut instancier et utiliser votre fonction add avec.

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, mais, ce qui est le paramètre de type lui-même. Comme il est écrit, la méthode manque sa classe. Le cas le plus probable est que vous appelez la méthode + sur une instance de T, en passant une autre instance de T. Je l'ai fait récemment, la définition d'un trait qui a dit: « un groupe additif est constitué d'une opération d'ajout, plus les moyens de inverser un élément "

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

Puis, plus tard, je définir une classe réel qui sait comment ajouter des instances de lui-même (étend le terrain GroupAdditive):

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

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

  ...
}

Cela peut être plus que vous vouliez vraiment savoir en ce moment, mais il montre à la fois comment définir des arguments génériques et comment les réaliser.

En fin de compte, les types ne sont pas nécessaires, mais le compilateur ne doit savoir au moins les limites de type.

scroll top