En fait, je pense que le polymorphisme de type retour est l'une des meilleures caractéristiques des classes de type. Après l'avoir utilisé pendant un certain temps, il m'est parfois difficile de revenir à la modélisation de style OOP où je ne l'ai pas.
Considérez le codage de l'algèbre. Dans Haskell, nous avons une classe de type Monoid
(ignorant mconcat
)
class Monoid a where
mempty :: a
mappend :: a -> a -> a
Comment pourrions-nous coder cela comme une interface dans un langage OO? La réponse courte est que nous ne pouvons pas. C'est parce que le type de polymorphisme mempty
est (Monoid a) => a
aka, de type retour. Avoir la capacité de modéliser l'algèbre est incroyablement utile OMI. *
Vous commencez votre message avec la plainte concernant la "transparence référentielle". Cela soulève un point important: Haskell est un langage axé sur les valeurs. Ainsi, les expressions comme read 3
ne doivent pas être comprises comme des choses qui calculent des valeurs, elles peuvent également être comprises comme des valeurs. Cela signifie que le vrai problème n'est pas le polymorphisme de type retour: ce sont les valeurs de type polymorphe ( []
et Nothing
). Si le langage doit les avoir, alors il doit vraiment avoir des types de retour polymorphes pour la cohérence.
Faut-il pouvoir dire []
est de type forall a. [a]
? Je le pense. Ces fonctionnalités sont très utiles et rendent le langage beaucoup plus simple.
Si Haskell avait un sous-type, le polymorphisme []
pourrait être un sous-type pour tous [a]
. Le problème est que je ne connais pas de moyen de coder cela sans que le type de la liste vide soit polymorphe. Considérez comment cela serait fait dans Scala (c'est plus court que de le faire dans le langage OOP canonique à typage statique, Java)
abstract class List[A]
case class Nil[A] extends List[A]
case class Cons[A](h: A. t: List[A]) extends List[A]
Même ici, Nil()
est un objet de type Nil[A]
**
Un autre avantage du polymorphisme de type retour est qu'il rend l'intégration de Curry-Howard beaucoup plus simple.
Considérez les théorèmes logiques suivants:
t1 = forall P. forall Q. P -> P or Q
t2 = forall P. forall Q. P -> Q or P
Nous pouvons trivialement les capturer comme théorèmes dans Haskell:
data Either a b = Left a | Right b
t1 :: a -> Either a b
t1 = Left
t2 :: a -> Either b a
t2 = Right
Pour résumer: j'aime le polymorphisme de type retour, et je pense qu'il brise la transparence référentielle uniquement si vous avez une notion limitée de valeurs (bien que ce soit moins convaincant dans le cas d'une classe de type ad hoc). D'un autre côté, je trouve vos points sur MR et le type par défaut convaincant.
*. Dans les commentaires, ysdx souligne que ce n'est pas strictement vrai: nous pourrions réimplémenter les classes de types en modélisant l'algèbre comme un autre type. Comme le Java:
abstract class Monoid<M>{
abstract M empty();
abstract M append(M m1, M m2);
}
Vous devez ensuite passer des objets de ce type avec vous. Scala a une notion de paramètres implicites qui évite certains, mais selon mon expérience pas tous, de la surcharge de gestion explicite de ces choses. Mettre vos méthodes utilitaires (méthodes d'usine, méthodes binaires, etc.) sur un type délimité F se révèle être une façon incroyablement agréable de gérer les choses dans un langage OO qui prend en charge les génériques. Cela dit, je ne suis pas sûr que j'aurais gâché ce modèle si je n'avais pas d'expérience de la modélisation de choses avec des classes de caractères, et je ne suis pas sûr que d'autres personnes le feront.
Il a également des limites, hors de la boîte il n'y a aucun moyen d'obtenir un objet qui implémente la classe de types pour un type arbitraire. Vous devez soit transmettre les valeurs explicitement, utiliser quelque chose comme les implicites de Scala, soit utiliser une forme de technologie d'injection de dépendance. La vie devient laide. En revanche, il est bien que vous puissiez avoir plusieurs implémentations pour le même type. Quelque chose peut être monoïde de plusieurs façons. En outre, le fait de transporter ces structures séparément a une impression plus moderne et constructive sur le plan mathématique. Donc, même si je préfère généralement la manière Haskell de le faire, j'ai probablement exagéré mon cas.
Les classes avec polymorphisme de type retour rendent ce genre de chose facile à manipuler. Cela ne veut pas dire que c'est la meilleure façon de le faire.
**. Jörg W Mittag souligne que ce n'est pas vraiment la manière canonique de procéder à Scala. Au lieu de cela, nous suivrions la bibliothèque standard avec quelque chose de plus comme:
abstract class List[+A] ...
case class Cons[A](head: A, tail: List[A]) extends List[A] ...
case object Nil extends List[Nothing] ...
Cela tire parti de la prise en charge de Scala pour les types de fond, ainsi que pour les paramètres de type covariant. Donc, Nil
c'est du type Nil
non Nil[A]
. À ce stade, nous sommes assez loin de Haskell, mais il est intéressant de noter comment Haskell représente le type inférieur
undefined :: forall a. a
Autrement dit, ce n'est pas le sous-type de tous les types, il est polymorphe (sp) un membre de tous les types.
Encore plus de polymorphisme de type retour.