Les étiquettes des syndicats étiquetés devraient être des valeurs de premier ordre, et avec un peu d'effort, elles le sont.
Alerte à la pyromanie :
{-# LANGUAGE GADTs, DataKinds, KindSignatures,
TypeFamilies, PolyKinds, FlexibleInstances,
PatternSynonyms
#-}
Première étape : définir des versions des balises au niveau du type.
data TagType = EmptyTag | SingleTag | PairTag | LotsTag
Deuxième étape : définir des témoins de niveau valeur pour la représentabilité des balises de niveau type. La bibliothèque Singletons de Richard Eisenberg le fera pour vous. Je veux dire quelque chose comme ça :
data Tag :: TagType -> * where
EmptyT :: Tag EmptyTag
SingleT :: Tag SingleTag
PairT :: Tag PairTag
LotsT :: Tag LotsTag
Et maintenant, nous pouvons dire quelles sont les choses que nous nous attendons à trouver associées à un tag donné.
type family Stuff (t :: TagType) :: * where
Stuff EmptyTag = ()
Stuff SingleTag = Int
Stuff PairTag = (Int, Int)
Stuff LotsTag = [Int]
Nous pouvons donc refactoriser le type auquel vous avez d'abord pensé
data NumCol :: * where
(:&) :: Tag t -> Stuff t -> NumCol
et utiliser PatternSynonyms
pour retrouver le comportement que vous aviez en tête :
pattern Empty = EmptyT :& ()
pattern Single i = SingleT :& i
pattern Pair i j = PairT :& (i, j)
pattern Lots is = LotsT :& is
Donc ce qui s'est passé c'est que chaque constructeur pour NumCol
s'est transformée en une balise indexée par le type de balise à laquelle elle correspond. En d'autres termes, les balises de constructeur sont maintenant séparées du reste des données, synchronisées par un index commun qui garantit que les éléments associés à une balise correspondent à la balise elle-même.
Mais nous pouvons parler uniquement des étiquettes.
data Ex :: (k -> *) -> * where -- wish I could say newtype here
Witness :: p x -> Ex p
Maintenant, Ex Tag
est le type de "balises d'exécution avec une contrepartie au niveau du type". Elle possède un Eq
instance
instance Eq (Ex Tag) where
Witness EmptyT == Witness EmptyT = True
Witness SingleT == Witness SingleT = True
Witness PairT == Witness PairT = True
Witness LotsT == Witness LotsT = True
_ == _ = False
De plus, nous pouvons facilement extraire la balise d'une NumCol
.
numColTag :: NumCol -> Ex Tag
numColTag (n :& _) = Witness n
Et cela nous permet de répondre à votre cahier des charges.
filter ((Witness PairT ==) . numColTag) :: [NumCol] -> [NumCol]
Ce qui soulève la question de savoir si votre spécification correspond réellement à ce dont vous avez besoin. Le fait est que la détection d'une balise vous donne le droit de vous attendre à ce que cette balise soit utilisée. Le type de sortie [NumCol]
ne rend pas justice au fait que vous savez que vous avez les bonnes paires.
Comment pouvez-vous resserrer le type de votre fonction et continuer à la fournir ?