web-dev-qa-db-fra.com

Quelle est la différence entre le polymorphisme paramétrique et les types de type supérieur?

Je suis sûr qu'ils ne sont pas les mêmes. Cependant, je suis embourbé par la notion courante que "Rust ne prend pas en charge" les types de type supérieur (HKT), mais offre à la place un polymorphisme paramétrique . J'ai essayé de comprendre ce qui se passait et de comprendre la différence entre ceux-ci, mais je me suis de plus en plus empêtré.

À ma connaissance, il existe des types de type supérieur dans Rust, au moins les bases. En utilisant la notation "*", un HKT a une sorte de par ex. * -> *. Par exemple, Maybe est de type * -> * et pourrait être implémenté comme ceci dans Haskell.

data Maybe a = Just a | Nothing

Ici,

  • Maybe est un constructeur de type et doit être appliqué à un type concret pour devenir un type concret du type "*".
  • Just a et Nothing sont des constructeurs de données.

Dans les manuels sur Haskell, cela est souvent utilisé comme exemple pour un type de type supérieur. Cependant, dans Rust il peut simplement être implémenté comme une énumération, qui après tout est un type de somme :

enum Maybe<T> {
    Just(T),
    Nothing,
}

Quelle est la différence? À ma connaissance, il s'agit d'un très bel exemple d'un type de type supérieur.

  1. Si dans Haskell ceci est utilisé comme un exemple de manuel de HKT, pourquoi est-il dit que Rust n'a pas de HKT? L'énumération Maybe n'est-elle pas qualifiée de HKT?
  2. Faut-il plutôt dire que Rust ne supporte pas complètement HKT?
  3. Quelle est la différence fondamentale entre le HKT et le polymorphisme paramétrique?

Cette confusion continue quand on regarde les fonctions, je peux écrire une fonction paramétrique qui prend un Maybe, et à ma connaissance un HKT comme argument de fonction.

fn do_something<T>(input: Maybe<T>) {
    // implementation
}

encore une fois, à Haskell, ce serait quelque chose comme

do_something :: Maybe a -> ()
do_something :: Maybe a -> ()
do_something _ = ()

ce qui conduit à la quatrième question.

  1. Où se termine exactement la prise en charge des types de type supérieur? Quel est l'exemple minimal pour que le système de type de Rust n'exprime pas HKT?

Questions connexes:

J'ai parcouru beaucoup de questions liées au sujet (y compris les liens qu'ils ont vers des articles de blog, etc.) mais je n'ai pas pu trouver de réponse à mes principales questions (1 et 2).

  1. Dans Haskell, les "types de type supérieur" * sont-ils vraiment * des types? Ou désignent-ils simplement des collections de types * concrets * et rien de plus?
  2. Structure générique sur un type générique sans paramètre de type
  3. Types de type supérieur dans Scala
  4. Quels types de problèmes aident le "polymorphisme de type supérieur" à mieux résoudre?
  5. Types de données abstraits vs polymorphisme paramétrique dans Haskell

Mise à jour

Merci pour les nombreuses bonnes réponses qui sont toutes très détaillées et qui ont beaucoup aidé. J'ai décidé d'accepter la réponse d'Andreas Rossberg car son explication m'a le plus aidé à me mettre sur la bonne voie. Surtout la partie sur la terminologie.

J'étais vraiment enfermé dans le cycle de penser que tout de même * -> * ... -> * est de type supérieur . L'explication qui a souligné la différence entre * -> * -> * et (* -> *) -> * était crucial pour moi.

34
StarSheriff

Quelques terminologies:

  • Le genre * est parfois appelé sol. Vous pouvez le considérer comme du 0e ordre.
  • Tout type de formulaire * -> * -> ... -> * avec au moins une flèche est premier ordre.
  • Un type d'ordre supérieur est celui qui a une "flèche imbriquée à gauche", par exemple, (* -> *) -> *.

ordre est essentiellement la profondeur d'imbrication du côté gauche des flèches, par exemple (* -> *) -> * est de second ordre, ((* -> *) -> *) -> * est de troisième ordre, etc. (FWIW, la même notion s'applique aux types eux-mêmes: une fonction de second ordre est une fonction dont le type a par exemple la forme (A -> B) -> C.)

Les types de type non terrestre (ordre> 0) sont également appelés type constructeurs (et certains documents ne font référence aux types de type terrestre que comme "types"). Un type de type supérieur (constructeur) est un type dont le type est d'ordre supérieur (ordre> 1).

Par conséquent, un type de type supérieur est celui qui prend un argument de type non fondamental. Cela nécessiterait des variables de type de type non sol, qui ne sont pas prises en charge dans de nombreuses langues. Exemples à Haskell:

type Ground = Int
type FirstOrder a = Maybe a  -- a is ground
type SecondOrder c = c Int   -- c is a first-order constructor
type ThirdOrder c = c Maybe  -- c is second-order

Les deux derniers sont de type supérieur.

De même, polymorphisme de type supérieur décrit la présence de valeurs polymorphes (paramétriques) qui s'abstiennent sur des types qui ne sont pas broyés. Encore une fois, peu de langues le supportent. Exemple:

f : forall c. c Int -> c Int  -- c is a constructor

La déclaration selon laquelle Rust prend en charge le polymorphisme paramétrique "au lieu" des types de type supérieur n'a pas de sens. Les deux sont des dimensions de paramétrage différentes qui se complètent. Et lorsque vous combinez les deux, vous avez un type plus élevé polymorphisme.

31
Andreas Rossberg

Un exemple simple de ce que Rust ne peut pas faire est quelque chose comme la classe Functor de Haskell.

class Functor f where
    fmap :: (a -> b) -> f a -> f b

-- a couple examples:
instance Functor Maybe where
    -- fmap :: (a -> b) -> Maybe a -> Maybe b
    fmap _ Nothing  = Nothing
    fmap f (Just x) = Just (f x)

instance Functor [] where
    -- fmap :: (a -> b) -> [a] -> [b]
    fmap _ []     = []
    fmap f (x:xs) = f x : fmap f xs

Notez que les instances sont définies sur le constructeur de type, Maybe ou [], au lieu du type entièrement appliqué Maybe a ou [a].

Ce n'est pas seulement un tour de salon. Il a une forte interaction avec le polymorphisme paramétrique. Étant donné que les variables de type a et b dans le type fmap ne sont pas limitées par la définition de classe, les instances de Functor ne peuvent pas modifier leur comportement en fonction de celles-ci. C'est une propriété incroyablement solide pour raisonner sur le code des types et d'où vient la force du système de types de Haskell.

Il a une autre propriété - vous pouvez écrire du code abstrait dans des variables de type supérieur. Voici quelques exemples:

focusFirst :: Functor f => (a -> f b) -> (a, c) -> f (b, c)
focusFirst f (a, c) = fmap (\x -> (x, c)) (f a)

focusSecond :: Functor f => (a -> f b) -> (c, a) -> f (c, b)
focusSecond f (c, a) = fmap (\x -> (c, x)) (f a)

J'admets, ces types commencent à ressembler à un non-sens abstrait. Mais ils s'avèrent vraiment pratiques lorsque vous avez quelques assistants qui profitent de l'abstraction de type supérieur.

newtype Identity a = Identity { runIdentity :: a }

instance Functor Identity where
    -- fmap :: (a -> b) -> Identity a -> Identity b
    fmap f (Identity x) = Identity (f x)

newtype Const c b = Const { getConst :: c }

instance Functor (Const c) where
    -- fmap :: (a -> b) -> Const c a -> Const c b
    fmap _ (Const c) = Const c

set :: ((a -> Identity b) -> s -> Identity t) -> b -> s -> t
set f b s = runIdentity (f (\_ -> Identity b) s)

get :: ((a -> Const a b) -> s -> Const a t) -> s -> a
get f s = getConst (f (\x -> Const x) s)

(Si j'ai fait des erreurs, quelqu'un peut-il les corriger? Je réimplémente le point de départ le plus basique de lens de la mémoire sans compilateur.)

Les fonctions focusFirst et focusSecond peuvent être passées comme premier argument à get ou set, car la variable de type f dans leur les types peuvent être unifiés avec les types plus concrets dans get et set. La possibilité d'abstraire sur la variable de type supérieur f permet d'utiliser des fonctions d'une forme particulière à la fois comme setters et getters dans des types de données arbitraires. C'est l'une des deux principales informations qui ont conduit à la bibliothèque lens. Il ne pourrait exister sans ce genre d'abstraction.

(Pour ce que ça vaut, l'autre idée clé est que la définition des lentilles comme une fonction comme celle-ci permet à la composition des lentilles d'être une composition de fonction simple.)

Donc non, il y a plus que simplement pouvoir accepter une variable de type. L'important est de pouvoir utiliser des variables de type qui correspondent aux constructeurs de type, plutôt que des types concrets (si inconnus).

16
Carl

Le polymorphisme paramétrique fait simplement référence à la propriété selon laquelle la fonction ne peut utiliser aucune caractéristique particulière d'un type (ou type) dans sa définition; c'est une boîte noire complète. L'exemple standard est length :: [a] -> Int, Qui ne fonctionne qu'avec structure de la liste, pas les valeurs particulières stockées dans la liste.

L'exemple standard de HKT est la classe Functor, où fmap :: (a -> b) -> f a -> f b. Contrairement à length, où a a un type *, f a un type * -> *. fmapégalement présente un polymorphisme paramétrique, car fmap ne peut utiliser aucune propriété de a ou b dans sa définition.

fmap présente également un polymorphisme ad hoc, car la définition peut être adaptée au constructeur de type spécifique f pour lequel elle est définie. Autrement dit, il existe des définitions distinctes de fmap pour f ~ [], f ~ Maybe, Etc. La différence est que f est "déclaré" dans le cadre de la classe de types définition, plutôt que de simplement faire partie de la définition de fmap. (En effet, des classes de caractères ont été ajoutées pour prendre en charge un certain degré de polymorphisme ad hoc. Sans classes de types, niquement le polymorphisme paramétrique existe. Vous pouvez écrire une fonction qui prend en charge n type concret ou - tout type de béton, mais pas une petite collection entre les deux.)

9
chepner

Je vais le reprendre: un type de type supérieur n'est qu'une fonction d'ordre supérieur de niveau type.

Mais prenez une minute:

Considérez les transformateurs monad:

newtype StateT s m a :: * -> (* -> *) -> * -> *

Ici,

- s is the desired type of the state
- m is a functor, another monad that StateT will wrap
- a is the return type of an expression of type StateT s m

Quel est le type de type supérieur?

m :: (* -> *)

Parce que prend un type de type * et renvoie une sorte de type *.

C'est comme une fonction sur les types, c'est-à-dire un constructeur de type

* -> *

Dans des langages comme Java, vous ne pouvez pas faire

class ClassExample<T, a> {
    T<a> function()
}

Dans Haskell, T aurait une sorte de *->*, mais un type Java (c.-à-d. classe)) ne peut pas avoir un paramètre de type de ce type, un type de type supérieur.

De plus, si vous ne savez pas, dans Haskell de base, une expression doit avoir un type qui a kind *, c'est-à-dire un "type concret". Tout autre type comme * -> *.

Par exemple, vous ne pouvez pas créer une expression de type Maybe. Il doit s'agir de types appliqués à un argument comme Maybe Int, Maybe String, etc. En d'autres termes, des constructeurs de type entièrement appliqués.

8
Federico Sawady