Je pense que c'est le plus loin dans les extensions de système de type Haskell que j'ai déjà été et j'ai couru dans une erreur que je n'ai pas pu se rendre compte. Excuses d'avance pour la longueur, c'est l'exemple le plus court que je puisse créer qui illustre encore le problème que j'ai. J'ai un GADT récursive dont type est une liste promu, quelque chose comme ce qui suit:Comment traiter un GADT récursif avec kind :: '[SomeDataKind]
GADT Définition
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeOperators #-}
data DataKind = A | B | C
-- 'parts' should always contain at least 1 element which is enforced by the GADT.
-- Lets call the first piece of data 'val' and the second 'subdata'.
-- All data constructors have these 2 fields, although some may have
-- additional fields which I've omitted for simplicity.
data SomeData (parts :: [DataKind]) where
MkA :: Maybe Char -> Maybe (SomeData subparts) -> SomeData ('A ': subparts)
MkB :: Maybe Char -> Maybe (SomeData subparts) -> SomeData ('B ': subparts)
MkC :: Maybe Char -> Maybe (SomeData subparts) -> SomeData ('C ': subparts)
deriving instance Show (SomeData parts)
Problème
Ce que je suis en train de faire est de parcourir les données et effectuer certaines opérations, par exemple propager le premier Just
Char
trouvé au sommet.
caractéristiques Annoying manquantes - nécessaires pour la section suivante
maintenant, parce qu'il n'y a apparemment pas de soutien de la syntaxe des enregistrements pour vous GADTs (https://ghc.haskell.org/trac/ghc/ticket/2595) besoin de les écrire manuellement, donc ils sont ici:
getVal :: SomeData parts -> Maybe Char
getVal (MkA val _) = val
getVal (MkB val _) = val
getVal (MkC val _) = val
-- The lack of record syntax for GADTs is annoying.
updateVal :: Maybe Char -> SomeData parts -> SomeData parts
updateVal val (MkA _val sub) = MkA val sub
updateVal val (MkB _val sub) = MkB val sub
updateVal val (MkC _val sub) = MkC val sub
-- really annoying...
getSubData :: SomeData (p ': rest) -> Maybe (SomeData rest)
getSubData (MkA _ sub) = sub
getSubData (MkB _ sub) = sub
getSubData (MkC _ sub) = sub
données de test
donc, la chose que je veux faire. Descendez la structure du haut jusqu'à ce que je trouve une valeur qui est Just
. Ainsi, compte tenu des valeurs initiales suivantes:
a :: SomeData '[ 'A ]
a = MkA (Just 'A') Nothing
b :: SomeData '[ 'B ]
b = MkB (Just 'B') Nothing
c :: SomeData '[ 'C ]
c = MkC (Just 'C') Nothing
bc :: SomeData '[ 'B, 'C ]
bc = MkB Nothing (Just c)
abc :: SomeData '[ 'A, 'B, 'C ]
abc = MkA Nothing (Just bc)
Résultat attendu
Je voudrais avoir quelque chose comme ceci:
> abc
MkA Nothing (Just (MkB Nothing (Just (MkC (Just 'C') Nothing))))
> propogate abc
MkA (Just 'C') (Just (MkB (Just 'C') (Just (MkC (Just 'C') Nothing))))
Tentatives
Je pris quelques coups de couteau à cela, d'abord avec une fonction régulière:
propogate sd =
case getVal sd of
Just _val ->
sd
Nothing ->
let
newSubData = fmap propogate (getSubData sd)
newVal = join . fmap getVal $ newSubData
in
updateVal newVal sd
Cela donne à l'erreur:
Broken.hs:(70,1)-(81,35): error: …
• Occurs check: cannot construct the infinite type: rest ~ p : rest
Expected type: SomeData rest -> SomeData (p : rest)
Actual type: SomeData (p : rest) -> SomeData (p : rest)
• Relevant bindings include
propogate :: SomeData rest -> SomeData (p : rest)
(bound at Broken.hs:70:1)
Compilation failed.
J'ai essayé aussi une classe de types et de tenter de faire correspondre à la structure:
class Propogate sd where
propogateTypeClass :: sd -> sd
-- Base case: We only have 1 element in the promoted type list.
instance Propogate (SomeData parts) where
propogateTypeClass sd = sd
-- Recursie case: More than 1 element in the promoted type list.
instance Propogate (SomeData (p ': parts)) where
propogateTypeClass sd =
case getVal sd of
Just _val ->
sd
Nothing ->
let
-- newSubData :: Maybe subparts
-- Recurse on the subdata if it exists.
newSubData = fmap propogateTypeClass (getSubData sd)
newVal = join . fmap getVal $ newSubData
in
updateVal newVal sd
Il en résulte l'erreur:
Broken.hs:125:5-26: error: …
• Overlapping instances for Propogate (SomeData '['A, 'B, 'C])
arising from a use of ‘propogateTypeClass’
Matching instances:
instance Propogate (SomeData parts)
-- Defined at Broken.hs:91:10
instance Propogate (SomeData (p : parts))
-- Defined at Broken.hs:95:10
• In the expression: propogateTypeClass abc
In an equation for ‘x’: x = propogateTypeClass abc
Compilation failed.
J'ai également essayé des combinaisons de correspondance sur SomeData '[]
et SomeData '[p]
en vain. J'espère avoir manqué quelque chose de simple mais je n'ai pas trouvé de documentation sur la façon de traiter une structure comme celle-ci et je suis à la limite de ma compréhension si le système de type Haskell, pour l'instant de toute façon:).Une fois de plus, désolé pour le poste long et toute aide serait grandement appréciée :)
Wow, merci pour la réponse incroyablement complète. Cela prend tout son sens et est tellement plus élégant, en particulier la partie 'DataPart'. Une question que j'ai à propos de DataPart est de savoir comment ajouter des données pour les autres types. Le changer pour 'A_Data :: ((x ==? 'A) ~' True)' avec similaire pour les autres ne fonctionne pas. –
Ah, je l'ai compris. 'A_Data :: ((x ==? 'A) ~' Vrai) => Entier -> DataPart x' fonctionne, mais' ... => Entier -> DataPart 'A' ne fonctionne pas. Je ne suis pas entièrement sûr pourquoi. –
La variable de type 'x' dans le constructeur' A_Data :: ((x ==? 'A) ~' Vrai) => ... -> DataPart 'A' n'est pas du tout lié à la variable de type dans l'index de type - cela ne fonctionne que parce que les variables de type sont implicitement liées, mais dans ces deux cas les variables de type jouent des rôles entièrement différents - dans le premier cas, le constructeur contient une contrainte sur un paramètre de type, un type quantifié existentiellement variable (avec une contrainte) ainsi que la preuve que le paramètre est "A" - mais la variable quantifiée existentiellement et le paramètre sont totalement indépendants. – user2407038