I have two types (<->)
and (<-->)
representing isomorphisms between types:
data Iso (m :: k -> k -> *) a b = Iso { to :: m a b, from :: m b a }
type (<->) = Iso (->)
infix 0 <->
data (<-->) a b = Iso' { to' :: a -> b, from' :: b -> a }
infix 0 <-->
The only difference between the two is that (<->)
is a specialization of a more general type.
I can coerce
(<-->)
isomorphisms easily:
coerceIso' :: (Coercible a a', Coercible b b') => (a <--> b) -> (a' <--> b')
coerceIso' = coerce
But I get an error when I try the same with (<->)
isomorphisms:
coerceIso :: (Coercible a a', Coercible b b') => (a <-> b) -> (a' <-> b')
coerceIso = coerce
{-
src/Data/Iso.hs:27:13: error:
• Couldn't match type ‘a’ with ‘a'’ arising from a use of ‘coerce’
‘a’ is a rigid type variable bound by
the type signature for:
coerceIso :: forall a a' b b'.
(Coercible a a', Coercible b b') =>
(a <-> b) -> a' <-> b'
at src/Data/Iso.hs:25:1-73
‘a'’ is a rigid type variable bound by
the type signature for:
coerceIso :: forall a a' b b'.
(Coercible a a', Coercible b b') =>
(a <-> b) -> a' <-> b'
at src/Data/Iso.hs:25:1-73
-}
My current work-around is to coerce the forwards and backwards functions separately:
coerceIso :: (Coercible a a', Coercible b b') => (a <-> b) -> (a' <-> b')
coerceIso (Iso f f') = Iso (coerce f) (coerce f')
But why is such a workaround is necessary? Why can't (<->)
be coerced directly?
The problem lies in the roles of the arguments m
in your general Iso
type.
Consider:
data T a b where
K1 :: Int -> T () ()
K2 :: String -> T () (Identity ())
type (<->) = Iso T
You can't really expect to be able to convert T () ()
into T () (Identity ())
even if ()
and Identity ()
are coercible.
You would need something like (pseudo code):
type role m representational representational =>
(Iso m) representational representational
but this can not be done in current Haskell, I believe.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With