ekmett / lens

Lenses, Folds, and Traversals - Join us on web.libera.chat #haskell-lens
http://lens.github.io/
Other
2.03k stars 273 forks source link

Add a `Wrapped` class? #141

Closed ekmett closed 11 years ago

ekmett commented 11 years ago

From a discussion on #haskell-lens, we should:

class Wrapped s t a b | a -> s, b -> t, a t -> s, b s -> t where
  wrapped :: Iso s t a b
  unwrapped :: Iso a b s t

wrapping :: Wrapped s s a a => (s -> a) -> Simple Iso s a
unwrapping :: Wrapped s s a a => (s -> a) -> Simple Iso a s

wrappings :: Wrapped s t a b => (s -> a) -> (t -> b) -> Iso s t a b
unwrappings :: Wrapped s t a b => (s -> a) -> (t -> b) -> Iso a b s t

instance Wrapped a b (Sum a) (Sum b) where
  wrapped = isos Sum getSum Sum getSum
...

This will greatly reduce the need for the million different newtype isomorphisms we have scattered throughout the codebase, clearing up the namespace for users.

mgsloan commented 11 years ago

For the record, here were some of the things that could then be deleted: https://github.com/ekmett/lens/commit/c2619c49fb1c2c5bfb660ac84dd7313241b015ed

Also, I thought about it a little more, and am now in favor of "wrap" and "unwrap", because they are used surprisingly little:

http://holumbus.fh-wedel.de/hayoo/hayoo.html#0:%22unwrap%22 http://holumbus.fh-wedel.de/hayoo/hayoo.html#0:%22wrap%22

The only one that's at all worrisome is both "unwrapping" and "unwrap" in the free library. I'm sure the author of that library won't mind aliasing his identifiers ;)

mgsloan commented 11 years ago

The other reason for my change of tack is that the Control.Newtype library provides many transformations that are more concise because they are specific to newtype Isos.

Control.Newtype Control.Lens using under using operators
op view . unwrap unwrap Foo ^.
over over . wrap under . unwrap wrap Foo %~
under over . unwrap under . wrap unwrap Foo %~
overF over . mapping . wrap under . mapping . unwrap mapping (wrap Foo) %~
underF over . mapping . unwrap under . mapping . wrap mapping (unwrap Foo) %~

We may also want to document these equivalences, for those who are already familiar with Control.Newtype, etc. I think they ought to be correct, but I could be wrong - I don't have GHC atm.

ekmett commented 11 years ago

I'm definitely tempted to add op.

I'm somewhat torn about alpha-reducing the names to wrap/unwrap, in part due to the conflict with the free package, which is heavily used.

mgsloan commented 11 years ago

EDIT: Nevermind, this would make it non-composable. hrmm

One way to avoid the unwrap / unwrapping issue is to change the type to Isomorphism, allowing the use of from. Is there a use case at all for a more generic Isomorphic k for newtypes?

This would make instances half as big and also remove the redundancy in the chart above - unwrap isn't actually useful! This would also motivate inclusion of op, as its definition would then be view . from . wrap

ekmett commented 11 years ago

Isomorphism no longer exists.

mgsloan commented 11 years ago

Really??? Should really update the code instead of just having gists ;)

Ahh, what about this solution?

class Wrapped s t a b | a -> s, b -> t, a t -> s, b s -> t where
  wrapped :: Isomorphism s t a b

wrap :: Wrapped s s a a => (s -> a) -> Simple Iso s a
wrap _ = via wrapped

wraps :: Wrapped s s a a => (s -> a) -> (t -> b) -> Iso s t a b
wraps _ _ = via wrapped

Then, the user can just do from (wrapped Sum), and there's no need for unwrap.

ekmett commented 11 years ago

I'm not too worried about taking unwrap if we decide to also take wrap.

I'm mostly on the fence about whether wrap is the name we want to take.

mgsloan commented 11 years ago

I'm more worried about the redundancy of the Wrapped class. It's very ugly, and means that there are more laws about it than there need to be. (better to have all of the laws just be expressed by types)

Isn't Isomorphism s t a b just Functor f => Forging f f s t a b?

class Wrapper s t a b | a -> s, b -> t, a t -> s, b s -> t where
  wrapper :: Isomorphism s t a b

wrapping :: Wrapped s s a a => (s -> a) -> Simple Iso s a
wrapping _ = via wrapper

wrappings :: Wrapped s t a b => (s -> a) -> (t -> b) -> Iso s t a b
wrappings _ _ = via wrapper

unwrapping :: Wrapped s s a a => (s -> a) -> Simple Iso a s
unwrapping _ = from wrapper

unwrappings :: Wrapped s t a b => (s -> a) -> (t -> b) -> Iso a b s t
unwrappings _ _ = from wrapper
ekmett commented 11 years ago

The Forging experiment didn't work, you may want to update your repo. The gist doesn't reflect reality. ;)

The current Iso design uses a data type named Isos in Control.Lens.Internal.

unwrapped = from wrapped

would be fine default definitions to add to the class or separate definition for unwrapping. Having both, while technically redundant makes for a nice vocabulary. I'm partial to splitting unwrapped out to a separate function so we get a single member typeclass, which optimizes differently.

This could reduce the repetition for end users somewhat an should definitely be done.

I would be rather strongly against removing the un- variants completely -- a minimalist vocabulary has never been a design goal of lenses.

On Nov 27, 2012, at 4:49 PM, mgsloan notifications@github.com wrote:

I'm more worried about the redundancy of the Wrapped class. It's very ugly, and means that there are more laws about it than there need to be. (better to have all of the laws just be expressed by types)

Isn't Isomorphism s t a b just Functor f => Forging f f s t a b?

class Wrapper s t a b | a -> s, b -> t, a t -> s, b s -> t where wrapper :: Isomorphism s t a b

wrapping :: Wrapped s s a a => (s -> a) -> Simple Iso s a wrapping _ = via wrapper

wrappings :: Wrapped s t a b => (s -> a) -> (t -> b) -> Iso s t a b wrappings = via wrapper

unwrapping :: Wrapped s s a a => (s -> a) -> Simple Iso a s unwrapping _ = from wrapper

unwrappings :: Wrapped s t a b => (s -> a) -> (t -> b) -> Iso a b s t unwrappings = from wrapper — Reply to this email directly or view it on GitHub.

mgsloan commented 11 years ago

So this is why "Isomorphism no longer exists" confuses me. Maybe it's just on the way out the door?

https://github.com/ekmett/lens/blob/master/src/Control/Lens/Iso.hs#L63

On Tue, Nov 27, 2012 at 2:18 PM, Edward A. Kmett notifications@github.comwrote:

The Forging experiment didn't work, you may want to update your repo. The gist doesn't reflect reality. ;)

The current Iso design uses a data type named Isos in Control.Lens.Internal.

unwrapped = from wrapped

would be fine default definitions to add to the class or separate definition for unwrapping. Having both, while technically redundant makes for a nice vocabulary. I'm partial to splitting unwrapped out to a separate function so we get a single member typeclass, which optimizes differently.

This could reduce the repetition for end users somewhat an should definitely be done.

I would be rather strongly against removing the un- variants completely -- a minimalist vocabulary has never been a design goal of lenses.

On Nov 27, 2012, at 4:49 PM, mgsloan notifications@github.com wrote:

I'm more worried about the redundancy of the Wrapped class. It's very ugly, and means that there are more laws about it than there need to be. (better to have all of the laws just be expressed by types)

Isn't Isomorphism s t a b just Functor f => Forging f f s t a b?

class Wrapper s t a b | a -> s, b -> t, a t -> s, b s -> t where wrapper :: Isomorphism s t a b

wrapping :: Wrapped s s a a => (s -> a) -> Simple Iso s a wrapping _ = via wrapper

wrappings :: Wrapped s t a b => (s -> a) -> (t -> b) -> Iso s t a b wrappings = via wrapper

unwrapping :: Wrapped s s a a => (s -> a) -> Simple Iso a s unwrapping _ = from wrapper

unwrappings :: Wrapped s t a b => (s -> a) -> (t -> b) -> Iso a b s t unwrappings = from wrapper — Reply to this email directly or view it on GitHub.

— Reply to this email directly or view it on GitHubhttps://github.com/ekmett/lens/issues/141#issuecomment-10780060.

ekmett commented 11 years ago

That isn't the Isomorphism we used to have. The old Isomorphism was very simple.

data Isomorphism a b = Isomorphism (a -> b) (b -> a)

The new Isomorphism can't be used directly to make a "reversible function" the way the old one could be abused - it only exists to facilitate pattern matching like how Getting, Projecting, etc. build on Accessor and Project.