My issue
I have the following type family that splits arguments off of a function:
type family
SeparateArgs
( a :: Type )
:: ( Type, [Type] )
where
SeparateArgs (a -> b) =
SndCons2 a (SeparateArgs b)
SeparateArgs a =
'(a, '[])
I also have this typeclass to do the reverse:
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 :>)
Now pretty much every time I use these two, I use them together like the following:
instance
( SeparateArgs a ~ '(goal, args)
, Refunct goal args a
, ...
)
=> ...
This is so I can break off the arguments, do some processing to create a function of the type HList args -> goal
and then turn it back into a regular function.
This works but it is pretty frustrating since I know that Separate a ~ '(goal, args) => Refunct args goal a
, meaning that only one of these statements is needed. However the compiler can't tell that so I need to inform it.
It is of course not critical, since my code currently works, but I would like to roll it into one statement. Ideally by adding a second functional dependency to Refunct
like so:
class
( SeparateArgs newSig ~ '(goal, args)
)
=> Refunct args goal newSig
| args goal -> newSig
, newSig -> args goal
where
refunct :: (HList args -> goal) -> newSig
(Of course this does not work on it's own)
Is there a way to reduce the two (the type family SeparateArgs
and the type class Refunct
) into a single constraint? I am still willing to define additional constructs I would just like to have non-redundant constraints in the result. I will still need the refunct
function.
If it is needed here is my implementation of 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
What I have tried
After discussing this elsewhere it was suggested I try:
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 :>)
Here we add an additional constrain that the first instance only works if IsAtomic goal ~ 'True
and the second only if IsAtomic goal ~ 'False
where IsAtomic
is a type family I've defined which is 'False
on functions and 'True
on everything else.
Here the compiler seems to be unable to confirm that the two instances do not violate the functional dependency. The exact error is:
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 it's not exact since I have removed all identifying info).
My intuition here is that it is not aware that IsAtomic goal ~ 'True
and IsAtomic goal ~ 'False
cannot both be true at the same time. This is reasonable since without inspection, we cannot know IsAtomic
is not forall a. a
which does satisfy both constraints.
*
is deprecated.import Data.Kind
and useType
instead. – HTNW