L'avenir est maintenant, lorsque vous (utilisez GHC 8 et) situé sur un drapeau ou deux
Prelude> :set -XPolyKinds -XFlexibleInstances
Déclarons
Prelude> newtype Flip f a b = MkFlip (f b a)
puis renseigner
Prelude> :kind Flip
Flip :: (k1 -> k -> *) -> k -> k1 -> *
Prelude> :type MkFlip
MkFlip
:: forall k k1 (b :: k) (f :: k -> k1 -> *) (a :: k1).
f b a -> Flip f a b
Le constructeur typeFlip
prend deux arguments implicites, étant k
et k1
, et trois arguments explicites, étant une fonction binaire produisant un type, puis ses deux arguments dans l'ordre inverse. Les arguments de cette fonction sont de type non contraint (les personnes âgées peuvent dire "gentil" si elles le veulent), mais il renvoie certainement un type (au sens strict de "chose dans *
", plutôt que le sens inutilement vague de "n'importe quel vieux ordures à droite de ::
") car il est certainement utilisé comme un type dans la déclaration de MkFlip
.
Le constructeur de données, MkFlip
, prend cinq arguments implicites (exactement les arguments des Flip
) et un argument explicite, étant des données dans f b a
.
Ce qui se passe est une inférence de type Hindley-Milner d'un niveau. Les contraintes sont recueillies (par exemple, f b a
doit habiter *
parce qu'un argument constructeur doit habiter f b a
) mais sinon un type plus général est livré: a
et b
pourrait être quelque chose, de sorte que leurs types sont généralisés comme k1
et k
.
Jouons le même jeu avec le constructeur de type constant:
Prelude> newtype K a b = MkK a
Prelude> :kind K
K :: * -> k -> *
Prelude> :type MkK
MkK :: forall k (b :: k) a. a -> K a b
On voit que a :: *
mais b
peut y avoir vieilleries (et pour cette matière, k :: *
, comme ces jours-ci, * :: *
). Clairement, a
est effectivement utilisé comme le type d'une chose, mais b
n'est pas utilisé du tout, donc sans contrainte.
On peut alors déclarer
Prelude> instance Functor (Flip K b) where fmap f (MkFlip (MkK a)) = MkFlip (MkK (f a))
et demander
Prelude> :info Flip
...
instance [safe] forall k (b :: k). Functor (Flip K b)
qui nous dit que le b
utilisé peut encore être une vieilleries. Parce que nous avions
K :: * -> k -> *
Flip :: (k1 -> k -> *) -> k -> k1 -> *
nous pouvons unifier k1 = *
et obtenir
Flip K :: k -> * -> *
et ainsi
Flip K b :: * -> *
pour tout vieux b
. Une instance Functor
est donc plausible, et en effet livrable, avec la fonction agissant sur l'élément a
empaqueté, correspondant à l'argument de Flip K b
qui devient le premier argument de K
, d'où le type de l'élément stocké.
L'inférence de type basée sur l'unification est vivante et (assez) bonne, droite de ::
.
J'ai mis à jour ma question. –
Est-ce que "fully grounded" est un terme technique? Je n'ai entendu que pleinement appliqué. Juste curieux ... – Alec
@Alec: non, c'était une erreur (trop dans Prolog :)) –