Mon problème
J'ai la famille de type suivante qui sépare les arguments d'une fonction :
type family
SeparateArgs
( a :: Type )
:: ( Type, [Type] )
where
SeparateArgs (a -> b) =
SndCons2 a (SeparateArgs b)
SeparateArgs a =
'(a, '[])
J'ai aussi cette classe de type pour faire l'inverse :
class Refunct args goal newSig | args goal -> newSig where
refunct :: (HList args -> goal) -> newSig
instance Refunct '[] goal goal where
refunct makeA = makeA HNil
instance
( Refunct tailArgs goal c
)
=> Refunct (headArg ': tailArgs) goal (headArg -> c)
where
refunct hFunct x = refunct $ hFunct . (x :>)
Maintenant, à peu près chaque fois que j'utilise ces deux-là, je les utilise ensemble comme suit :
instance
( SeparateArgs a ~ '(goal, args)
, Refunct goal args a
, ...
)
=> ...
Ainsi, je peux séparer les arguments, effectuer un traitement pour créer une fonction du type HList args -> goal
et ensuite le transformer en une fonction régulière.
Cela fonctionne mais c'est assez frustrant car je sais que Separate a ~ '(goal, args) => Refunct args goal a
ce qui signifie qu'une seule de ces déclarations est nécessaire. Cependant, le compilateur ne peut pas le savoir et je dois donc l'en informer.
Ce n'est bien sûr pas critique, puisque mon code fonctionne actuellement, mais je voudrais le regrouper en une seule déclaration. Idéalement en ajoutant une seconde dépendance fonctionnelle à Refunct
comme ça :
class
( SeparateArgs newSig ~ '(goal, args)
)
=> Refunct args goal newSig
| args goal -> newSig
, newSig -> args goal
where
refunct :: (HList args -> goal) -> newSig
(Bien sûr, cela ne fonctionne pas tout seul).
Existe-t-il un moyen de réduire les deux (la famille de type SeparateArgs
et la classe de type Refunct
) en une seule contrainte ? Je suis toujours prêt à définir des constructions supplémentaires, je voudrais simplement avoir des contraintes non redondantes dans le résultat. J'aurai toujours besoin de la refunct
fonction.
Si cela est nécessaire, voici mon implémentation de HList
:
data HList (a :: [ Type ]) where
HNil :: HList '[]
(:>) :: a -> HList b -> HList (a ': b)
hHead :: HList (a ': b) -> a
hHead (a :> _) = a
hTail :: HList (a ': b) -> HList b
hTail (_ :> b) = b
Ce que j'ai essayé
Après en avoir discuté ailleurs, il m'a été suggéré d'essayer :
type family
IsAtomic
( a :: Type )
:: Bool
where
IsAtomic (a -> b) = 'False
IsAtomic a = 'True
class
Refunct args goal newSig
| args goal -> newSig
, newSig -> args goal
where
refunct :: (HList args -> goal) -> newSig
instance
( IsAtomic goal ~ 'True
)
=> Refunct '[] goal goal
where
refunct makeA = makeA HNil
instance
( Refunct tailArgs goal c
, IsAtomic (headArg -> c) ~ 'False
)
=> Refunct (headArg ': tailArgs) goal (headArg -> c)
where
refunct hFunct x = refunct $ hFunct . (x :>)
Nous ajoutons ici une contrainte supplémentaire selon laquelle la première instance ne fonctionne que si IsAtomic goal ~ 'True
et le second seulement si IsAtomic goal ~ 'False
donde IsAtomic
est une famille de type que j'ai défini qui est 'False
sur les fonctions et 'True
sur tout le reste.
Ici, le compilateur semble incapable de confirmer que les deux instances ne violent pas la dépendance fonctionnelle. L'erreur exacte est la suivante :
Functional dependencies conflict between instance declarations:
instance (IsAtomic goal ~ 'True) => Refunct '[] goal goal
instance (Refunct tailArgs goal c, IsAtomic goal ~ 'False) =>
Refunct (headArg : tailArgs) goal (headArg -> c)
|
XXX | ( IsAtomic goal ~ 'True
| ^^^^^^^^^^^^^^^^^^^^^^^...
(ok ce n'est pas exact puisque j'ai enlevé toutes les informations d'identification).
Mon intuition ici est qu'il n'est pas conscient que IsAtomic goal ~ 'True
y IsAtomic goal ~ 'False
ne peuvent pas être toutes deux vraies en même temps. C'est raisonnable puisque sans inspection, nous ne pouvons pas savoir IsAtomic
n'est pas forall a. a
qui satisfait les deux contraintes.