Comme l'a fait remarquer @Michael Kohl, cette utilisation de forall en Haskell est un type existentiel et peut être exactement reproduite en Scala en utilisant soit la construction forSome, soit un joker. Cela signifie que la réponse de @paradigmatic est largement correcte.
Néanmoins, il manque quelque chose par rapport à l'original Haskell, à savoir que les instances de son type ShowBox capturent également les instances de la classe de type Show correspondante d'une manière qui les rend disponibles pour être utilisées sur les éléments de la liste, même lorsque le type sous-jacent exact a été existentiellement quantifié. Votre commentaire sur la réponse de @paradigmatic suggère que vous voulez être capable d'écrire quelque chose d'équivalent au Haskell suivant,
data ShowBox = forall s. Show s => ShowBox s
heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]
useShowBox :: ShowBox -> String
useShowBox (ShowBox s) = show s
-- Then in ghci ...
*Main> map useShowBox heteroList
["()","5","True"]
La réponse de @Kim Stebel montre la manière canonique de faire cela dans un langage orienté objet en exploitant le sous-typage. Toutes choses étant égales par ailleurs, c'est la bonne façon de procéder en Scala. Je suis sûr que vous le savez, et que vous avez de bonnes raisons de vouloir éviter le sous-typage et reproduire l'approche basée sur les classes de types de Haskell en Scala. C'est parti...
Notez que dans le Haskell ci-dessus, les instances de la classe de type Show pour Unit, Int et Bool sont disponibles dans l'implémentation de la fonction useShowBox. Si nous essayons de traduire directement cela en Scala, nous obtiendrons quelque chose comme ,
trait Show[T] { def show(t : T) : String }
// Show instance for Unit
implicit object ShowUnit extends Show[Unit] {
def show(u : Unit) : String = u.toString
}
// Show instance for Int
implicit object ShowInt extends Show[Int] {
def show(i : Int) : String = i.toString
}
// Show instance for Boolean
implicit object ShowBoolean extends Show[Boolean] {
def show(b : Boolean) : String = b.toString
}
case class ShowBox[T: Show](t:T)
def useShowBox[T](sb : ShowBox[T]) = sb match {
case ShowBox(t) => implicitly[Show[T]].show(t)
// error here ^^^^^^^^^^^^^^^^^^^
}
val heteroList: List[ShowBox[_]] = List(ShowBox(()), ShowBox(5), ShowBox(true))
heteroList map useShowBox
et cela échoue à compiler dans useShowBox comme suit,
<console>:14: error: could not find implicit value for parameter e: Show[T]
case ShowBox(t) => implicitly[Show[T]].show(t)
^
Le problème ici est que, contrairement au cas de Haskell, les instances de la classe de type Show ne sont pas propagées de l'argument ShowBox au corps de la fonction useShowBox, et ne sont donc pas disponibles pour être utilisées. Si nous essayons de résoudre ce problème en ajoutant un contexte supplémentaire à la fonction useShowBox,
def useShowBox[T : Show](sb : ShowBox[T]) = sb match {
case ShowBox(t) => implicitly[Show[T]].show(t) // Now compiles ...
}
cela corrige le problème dans useShowBox, mais maintenant nous ne pouvons pas l'utiliser en conjonction avec map sur notre liste existentiellement quantifiée,
scala> heteroList map useShowBox
<console>:21: error: could not find implicit value for evidence parameter
of type Show[T]
heteroList map useShowBox
^
En effet, lorsque useShowBox est fourni comme argument à la fonction map, nous devons choisir une instance de Show sur la base des informations de type dont nous disposons à ce moment-là. Il est clair qu'il n'y a pas une seule instance Show qui fera l'affaire pour tous les éléments de cette liste et donc la compilation échoue (si nous avions défini une instance Show pour Any, il y en aurait une, mais ce n'est pas ce que nous recherchons ici ... nous voulons sélectionner une instance de classe de type basée sur le type le plus spécifique de chaque élément de la liste).
Pour que cela fonctionne de la même manière qu'en Haskell, nous devons propager explicitement les instances Show dans le corps de useShowBox. Cela pourrait se passer comme suit,
case class ShowBox[T](t:T)(implicit val showInst : Show[T])
val heteroList: List[ShowBox[_]] = List(ShowBox(()), ShowBox(5), ShowBox(true))
def useShowBox(sb : ShowBox[_]) = sb match {
case sb@ShowBox(t) => sb.showInst.show(t)
}
puis dans le REPL,
scala> heteroList map useShowBox
res7: List[String] = List((), 5, true)
Notez que nous avons supprimé le contexte lié à ShowBox de sorte que nous avons un nom explicite (showInst) pour l'instance Show pour la valeur contenue. Ensuite, dans le corps de useShowBox, nous pouvons l'appliquer explicitement. Notez également que la correspondance de motif est essentielle pour garantir que nous n'ouvrons le type existentiel qu'une seule fois dans le corps de la fonction.
Comme vous pouvez le constater, cette solution est beaucoup plus compliquée que la solution équivalente en Haskell, et je vous recommande vivement d'utiliser la solution basée sur les sous-types en Scala, à moins que vous n'ayez de très bonnes raisons de faire autrement.
Modifier
Comme indiqué dans les commentaires, la définition Scala de ShowBox ci-dessus comporte un paramètre de type visible qui n'est pas présent dans l'original Haskell. Je pense qu'il est en fait assez instructif de voir comment nous pouvons rectifier cela en utilisant des types abstraits.
Tout d'abord, nous remplaçons le paramètre de type par un membre de type abstrait et nous remplaçons les paramètres du constructeur par des vals abstraits,
trait ShowBox {
type T
val t : T
val showInst : Show[T]
}
Nous devons maintenant ajouter la méthode d'usine que les classes de cas nous fourniraient gratuitement,
object ShowBox {
def apply[T0 : Show](t0 : T0) = new ShowBox {
type T = T0
val t = t0
val showInst = implicitly[Show[T]]
}
}
Nous pouvons maintenant utiliser ShowBox ordinaire là où nous utilisions précédemment ShowBox[_] ... le membre du type abstrait joue le rôle du quantificateur existentiel pour nous maintenant,
val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))
def useShowBox(sb : ShowBox) = {
import sb._
showInst.show(t)
}
heteroList map useShowBox
(Il est intéressant de noter qu'avant l'introduction d'explict forSome et de wildcards dans Scala, c'était exactement la façon dont on représentait les types existentiels).
Nous avons maintenant l'existentiel exactement au même endroit qu'il se trouve dans le Haskell original. Je pense que c'est le plus proche d'un rendu fidèle que l'on puisse obtenir en Scala.