Certains langages permettent des classes et des fonctions avec des paramètres de type (comme List<T>
où T
peut être un type arbitraire). Par exemple, vous pouvez avoir une fonction comme:
List<S> Function<S, T>(List<T> list)
Cependant, certaines langues permettent d'étendre ce concept d'un niveau supérieur, vous permettant d'avoir une fonction avec la signature:
K<S> Function<K<_>, S, T>(K<T> arg)
Où K<_>
lui-même est un type comme List<_>
celui qui a un paramètre de type. Ce "type partiel" est appelé constructeur de type.
Ma question est, pourquoi avez-vous besoin de cette capacité? Il est logique d'avoir un type comme List<T>
parce que tous List<T>
sont presque exactement les mêmes, mais tous K<_>
peuvent être entièrement différents. Vous pouvez avoir un Option<_>
et un List<_>
qui n'ont aucune fonctionnalité commune.
Functor
exemple de la réponse de Luis Casillas est assez intuitif. Qu'ontList<T>
etOption<T>
ont en commun? Si vous me donnez une et une fonction,T -> S
je peux vous donner unList<S>
ouOption<S>
. Une autre chose qu'ils ont en commun est que vous pouvez essayer d'obtenir uneT
valeur des deux.IReadableHolder<T>
.IMappable<K<_>, T>
la méthodeK<S> Map(Func<T, S> f)
, la mise en œuvre commeIMappable<Option<_>, T>
,IMappable<List<_>, T>
. Il faudrait donc contraindreK<T> : IMappable<K<_>, T>
à en tirer le meilleur parti.Réponses:
Comme personne d'autre n'a répondu à la question, je pense que je vais essayer moi-même. Je vais devoir devenir un peu philosophique.
La programmation générique concerne l'abstraction sur des types similaires, sans perte d'informations de type (ce qui se produit avec le polymorphisme de valeur orienté objet). Pour ce faire, les types doivent nécessairement partager une sorte d'interface (un ensemble d'opérations, pas le terme OO) que vous pouvez utiliser.
Dans les langages orientés objet, les types satisfont une interface grâce aux classes. Chaque classe a sa propre interface, définie comme faisant partie de son type. Étant donné que toutes les classes
List<T>
partagent la même interface, vous pouvez écrire du code qui fonctionne quel que soitT
votre choix. Une autre façon d'imposer une interface est une contrainte d'héritage, et bien que les deux semblent différentes, elles sont en quelque sorte similaires si vous y réfléchissez.Dans la plupart des langages orientés objet,
List<>
n'est pas un type approprié en soi. Il n'a pas de méthodes et n'a donc pas d'interface. C'est seulementList<T>
cela qui a ces choses. Essentiellement, en termes plus techniques, les seuls types sur lesquels vous pouvez résumer de manière significative sont ceux avec le type*
. Afin d'utiliser des types de type supérieur dans un monde orienté objet, vous devez formuler des contraintes de type d'une manière cohérente avec cette restriction.Par exemple, comme mentionné dans les commentaires, nous pouvons voir
Option<>
etList<>
comme "mappable", en ce sens que si vous avez une fonction, vous pouvez convertir unOption<T>
en unOption<S>
, ou unList<T>
en unList<S>
. En se rappelant que les classes ne peuvent pas être utilisées pour abstraire directement des types de type supérieur, nous faisons plutôt une interface:Et puis nous implémentons l'interface à la fois
List<T>
et en tantOption<T>
queIMappable<List<_>, T>
etIMappable<Option<_>, T>
respectivement. Ce que nous avons fait, c'est d'utiliser des types de type supérieur pour placer des contraintes sur les types réels (non de type supérieur)Option<T>
etList<T>
. C'est ainsi que cela se fait dans Scala, bien que Scala ait bien sûr des caractéristiques telles que des traits, des variables de type et des paramètres implicites qui le rendent plus expressif.Dans d'autres langues, il est possible d'abstraire directement des types de type supérieur. Dans Haskell, l'une des plus hautes autorités sur les systèmes de types, nous pouvons formuler une classe de type pour n'importe quel type, même si elle a un type supérieur. Par exemple,
Il s'agit d'une contrainte placée directement sur un type (non spécifié)
mp
qui prend un paramètre de type et nécessite qu'il soit associé à la fonctionmap
qui transforme unmp<a>
en unmp<b>
. Nous pouvons alors écrire des fonctions qui contraignent des types de plus haut niveau,Mappable
tout comme dans les langages orientés objet, vous pouvez placer une contrainte d'héritage. Eh bien, en quelque sorte.Pour résumer, votre capacité à utiliser des types de type supérieur dépend de votre capacité à les contraindre ou à les utiliser dans le cadre de contraintes de type.
la source
(Mappable mp) => mp a -> mp b
, vous avez placé une contraintemp
pour être membre de la classe de typeMappable
. Lorsque vous déclarez un type tel qu'ilOption
est une instance deMappable
, vous ajoutez un comportement à ce type. Je suppose que vous pouvez utiliser ce comportement localement sans jamais contraindre aucun type, mais ce n'est pas différent de définir une fonction ordinaire.*
sans les rendre inutilisables. Il est certainement vrai que les classes de types sont très puissantes lorsque vous travaillez avec des types de type supérieur.