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.
Maybe
n'est-elle pas qualifiée de HKT?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.
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).
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.
Quelques terminologies:
*
est parfois appelé sol. Vous pouvez le considérer comme du 0e ordre.* -> * -> ... -> *
avec au moins une flèche est premier ordre.(* -> *) -> *
.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.
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).
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.)
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.