TH-generated lenses on sum types throw `error` #29

Closed
jberryman opened this Issue Sep 2, 2012 · 15 comments

Comments

Projects
None yet
5 participants

...as you're aware ;-) But I didn't see the issue of partial lenses addressed in the docs or tutorial, so I thought opening a ticket would be best, if only as a place to address the issue once.

I want to build additional abstractions on top of lenses, but I don't find the current behavior of lens w/r/t sum types to be acceptable. Here are my questions:

  1. Is there a way to support "partial lenses" with maybe a less general constraint, like Applicative or something (sorry, I'm still getting oriented to this lens flavor)
  2. If so are there any plans to do this here, and if not, why not?

I'm aware that the usual "lens laws" don't really permit lenses on types with multiple constructors, but that seems more of an argument for coming up with a better lens-like thing or better laws, rather than sort of ignoring the issue.

Collaborator

pthariensflame commented Sep 2, 2012

I think that would be a Traversal; see #24.

Collaborator

mgsloan commented Sep 2, 2012

Hello!
I'm currently working on the TH generation for this. I have something that
works for constructors that lack fields, but still working on extending
this to handling multiple fields in one constructor mapping to the same
traversal. It wouldn't be very complicated, if not for the fact that we
need to output type signatures, and support configurability regarding
whether to generate traversals, runtime errors, or compiletime errors.

Hoping to push it out today, actually!
On Sep 1, 2012 11:56 PM, "Alexander Altman" notifications@github.com
wrote:

I think that would be a Traversal; see #24#24
.


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

Collaborator

mgsloan commented Sep 3, 2012

Here's the diff of the change:

https://github.com/ekmett/lens/pull/30/files

Unfortunately, I can't test yet due to what looks like a bug in my version of GHC7.6

@mgsloan mgsloan closed this Sep 3, 2012

Owner

ekmett commented Sep 3, 2012

@jberryman Traversals are the story for dealing with lenses that can view 0 or more entries of a given type.

There are combinators like ^? for attempting to access the (first) target of a traversal, and all of the setter combinators are generalized so they can take traversals, and you can do pretty much anything you can do with a Traversable container when given a Traversal.

There is a more refined 'single target' notion of a partial lens one can construct using pointed functors, e.g. Data.Pointed from pointed, but it is unfortunately not compatible with the rest of the lenses automatically lifting in to it, etc. because Pointed is not a superclass of Applicative. The idiom we've adopted instead is to simply expect a partial lens and use ^? or nullOf to check to see if it has a target. lengthOf can be used to check if it would hit multiple targets as well.

@mgsloan has been adding support for automatically generating Traversals to the TH code generator. Right now the template haskell code generator generates lenses that error out when you attempt to use them in a partial configuration. I'll probably take a whack at doing proper unification on them once his patch compiles.

Thanks for clarifying!
On Sep 2, 2012 11:07 PM, "Edward A. Kmett" notifications@github.com wrote:

@jberryman https://github.com/jberryman Traversals are the story for
dealing with lenses that can view 0 or more entries of a given type.

There are combinators like ^? for attempting to access the (first) target
of a traversal, and all of the setter combinators are generalized so they
can take traversals, and you can do pretty much anything you can do with a
Traversable container when given a Traversal.

There is a more refined 'single target' notion of a partial lens one can
construct using pointed functors, e.g. Data.Pointed from 'pointed', but it
is unfortunately not compatible with the rest of the lenses automatically
lifting in to it, etc. because Pointed is not a superclass of Applicative.
The idiom we've adopted instead is to simply expect a partial lens and use
^? or nullOf to check to see if it has a target. lengthOf can be used to
check if it would hit multiple targets as well.

@mgsloan https://github.com/mgsloan has been adding support for
automatically generating Traversals to the TH code generator. Right now the
template haskell code generator generates lenses that error out when you
attempt to use them in a partial configuration. I'll probably take a whack
at doing proper unification on them once his patch compiles.


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

Owner

ekmett commented Sep 3, 2012

I just pushed out version 2.6. It now supports generating a Traversal rather than an error by default when you use makeLenses. (Thanks @mgsloan !).

@ekmett : I hate to pollute the issue tracker, but I wonder if you'd be able to look at my use case. I have a lens-based zipper library (not well-documented yet, but quick walkthrough here). To use lens for the underlying lens representation, I need to either:

  1. be able to recover something like a -> Maybe (b , b -> a) from a Traversal (or equivalent)
  2. do something else clever I haven't thought of yet

It looks like (1) would be possible with Pointed, but not with Applicative. If you have any interest, or thoughts let me know, otherwise please feel free to ignore.

Collaborator

jfischoff commented Sep 18, 2012

+1 on a solution to this whether that solution might be...

On Tue, Sep 18, 2012 at 9:11 AM, Brandon Simmons
notifications@github.comwrote:

@ekmett https://github.com/ekmett : I hate to pollute the issue
tracker, but I wonder if you'd be able to look at my use case. I have a
lens-based zipper library http://hackage.haskell.org/package/zippo-0.1(not well-documented yet, but quick walkthrough
here http://brandon.si/code/zippo/). To use lens for the underlying
lens representation, I need to either:

be able to recover something like a -> Maybe (b , b -> a) from a
Traversal (or equivalent)
2.

do something else clever I haven't thought of yet

It looks like (1) would be possible with Pointed, but not with Applicative.
If you have any interest, or thoughts let me know, otherwise please feel
free to ignore.


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

Owner

ekmett commented Sep 18, 2012

You can extract the first result targeted by a Simple Traversal a b as an a -> Maybe b, using (^?) and you can update (all of) the target(s) of the Traversal with (.~).

To edit precisely that one target, we can do something a little more refined:

There are a few variations on this theme that are possible with the existing combinators in lens.

Rather than pass stuff like (b, b -> a), I use an indexed variation on the Store or Costate comonad, that is exported by Control.Lens.Internal. (I should probably add it to the exports of Control.Lens as it is visible in the API for Plated)

data Context c d a = Context (d -> a) c

You can use holesOf from Control.Lens.Plated to take a Traversal and turn it into a list of editable contexts.

Then

listToMaybe.holesOf :: Simple Traversal a b -> a -> Maybe (Context b b a)

is exactly what you asked for, except now the contexts form a Comonad, and you can use the operations from Control.Comonad.Store to peek at the result that you'd get if you edited the value, read b at the current pos, etc.

Regarding why there isn't a partial lens:

In order to keep it so people can create a Traversal without incurring a dependency on this package, I deliberately avoided creating a Pointed/partial lens family, which is what you'd get if you used

type PartialLens a b c d = forall f. (Pointed f, Functor f) => (c -> f d) -> a -> f b

The motivation is that you can always abuse a Monoid like First to pick the first entry to edit.

[Edit: I apparently had explained this last part earlier in the thread and forgotten. The repetition of some kind of 'party line' wasn't intentional.] =)

@ghost ghost assigned ekmett and mgsloan Sep 18, 2012

Owner

ekmett commented Sep 18, 2012

Re: Creating extra issues. Don't worry about polluting the bug tracker. I'd rather have extra top level issues that can be easily closed out as duplicates and cross-referenced than have monolithic issues that entangle multiple concerns. =)

Collaborator

mgsloan commented Sep 18, 2012

Maybe it'd be good at some point to start collating together "recipes"?
Common usages of combinators together. While the fact that such recipes
would exist may be disturbing (suggesting that a new function should
exist!), it's fairly necessary if you want to avoid full combinator
explosion ;)

On Tue, Sep 18, 2012 at 11:56 AM, Edward A. Kmett
notifications@github.comwrote:

Re: Creating extra issues. Don't worry about polluting the bug tracker.
I'd rather have extra top level issues that can be easily closed out as
duplicates and cross-referenced than have monolithic issues that entangle
multiple concerns. =)


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

Owner

ekmett commented Sep 18, 2012

Sounds good. Feel free to add a page to the wiki. =)

Owner

ekmett commented Sep 18, 2012

@jberryman: see ekmett/zippo -- It may be what you are looking for. I'm thinking about playing with the representation though to enable lateral moves through a Traversal. That way moving down into a traversal will actually move you down into its left-most position, and you can move left or right through the current traversal as well as up and down explicitly.

Wow, Edward, great stuff! It'll take me a bit to understand the mechanics in your lens-ified fork, but I really like what I see in your first commit and hope to merge it; and the lateral movement modification in your second is intriguing. Thanks again!

Owner

ekmett commented Sep 19, 2012

I'm currently trying to see if I can get saving and restoring working in the manner of pez as well.

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