Closed treeowl closed 9 years ago
A slightly prettier expression:
coercionToIso' c = rmap (fmap . coerceWith $ sym c) . lmap (coerceWith c)
Ah, but the prettier one is probably lazy in an unfortunate way.
rmap f . lmap g
should be able to be rewritten in terms of dimap
.
The major concern I'd have about such a combinator is how bad inference will be when using it in practice.
Using Control.Lens.Internal.Coerce
to get coerce'
we have:
iso coerce coerce' :: (Coercible t b, Coercible s a) => Iso s t a b
Using coerce'
reverses the second Coercible
constraint there so that when we instantiate s = t
, a = b
, this becomes the definition you gave, but subsumes it otherwise.
There are a few options for the more general combinator, unfortunately. One is to use the fact that Coercible
instances floating in the air generally come in pairs, so instead of
coercibleIso1 :: forall s t a b . (Coercible t b, Coercible s a) => Iso s t a b
coercibleIso1 = dimap coerce (fmap (coerce (id :: t -> t)))
we could use
coercibleIso2 :: forall s t a b . (Coercible b t, Coercible s a) => Iso s t a b
coercibleIso2 = dimap coerce (fmap coerce)
Inference does seem likely to be on the poor side, but I don't think that's a reason to exclude these—someone who wants one can just use a type signature. The Iso'
version,
coercibleIso' :: forall a b . Coercible a b => Iso' a b
coercibleIso' = dimap coerce (fmap (coerce (id :: a -> a)))
seems likely to offer considerably better inference. I am a bit concerned about the core I see without specialization. I get
coercibleIso'
:: forall a_a4T0 b_a4T1.
Coercible a_a4T0 b_a4T1 =>
Iso' a_a4T0 b_a4T1
coercibleIso' =
\ (@ a_a56o)
(@ b_a56p)
($dCoercible_a5bz :: Coercible a_a56o b_a56p)
(@ (p_a5bC :: * -> * -> *))
(@ (f_a5bD :: * -> *))
($dProfunctor_a5bE :: Profunctor p_a5bC)
($dFunctor_a5bF :: Functor f_a5bD) ->
dimap
$dProfunctor_a5bE
(\ (tpl_B2 :: a_a56o) ->
case $dCoercible_a5bz of _ { MkCoercible tpl1_B3 ->
tpl_B2 `cast` ...
})
(fmap
$dFunctor_a5bF
(case $dCoercible_a5bz of _ { MkCoercible $dCoercible2_d5mJ ->
(id) `cast` ...
}))
with the Coercible
"dictionaries" unpacked on the wrong side of the dimap
. I don't know why that's happening.
Using coerce'
there means that in the simple case where you use it as a getter
, or monomorphically you only wind up needing to construct and pass the one constraint.
The combinators that consume a getter all do that unification for you. This is why view
requires both arguments to match. Otherwise things like _JSON
which pick up a FromJSON
constraint on one side and a ToJSON
constraint on the other would require tons of type signatures.
We can optimize this further by using something like
coerce #. lmap (fmap coerce')
This will lift the one coerce
out of the Profunctor
.
Makes sense, although I still don't understand why the MkCoercible
gets unpacked so late.
I couldn't find anything like this, which of course doesn't mean there isn't such.
It might also be useful to offer this "out of the air" version: