Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add coercion Iso #579

Closed
treeowl opened this issue Aug 2, 2015 · 7 comments
Closed

Add coercion Iso #579

treeowl opened this issue Aug 2, 2015 · 7 comments

Comments

@treeowl
Copy link
Contributor

treeowl commented Aug 2, 2015

I couldn't find anything like this, which of course doesn't mean there isn't such.

coercionToIso' :: Coercion a b -> Iso' a b
coercionToIso' c = case (c, sym c) of
                     (Coercion, Coercion) ->
                         rmap (fmap coerce) . lmap coerce

It might also be useful to offer this "out of the air" version:

coercibleIso' :: Coercible a b => Iso' a b
coercibleIso' = coercionToIso' Coercion
@treeowl
Copy link
Contributor Author

treeowl commented Aug 2, 2015

A slightly prettier expression:

coercionToIso' c =  rmap (fmap . coerceWith $ sym c) . lmap (coerceWith c)

@treeowl
Copy link
Contributor Author

treeowl commented Aug 3, 2015

Ah, but the prettier one is probably lazy in an unfortunate way.

@ekmett
Copy link
Owner

ekmett commented Aug 3, 2015

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.

@treeowl
Copy link
Contributor Author

treeowl commented Aug 3, 2015

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.

@ekmett
Copy link
Owner

ekmett commented Aug 3, 2015

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.

@ekmett
Copy link
Owner

ekmett commented Aug 3, 2015

We can optimize this further by using something like

coerce #. lmap (fmap coerce')

This will lift the one coerce out of the Profunctor.

@treeowl
Copy link
Contributor Author

treeowl commented Aug 3, 2015

Makes sense, although I still don't understand why the MkCoercible gets unpacked so late.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants