Skip to content
This repository

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

Closed
jberryman opened this Issue September 01, 2012 · 15 comments

5 participants

Brandon Simmons Michael Sloan Edward Kmett Alexander Altman Jonathan Fischoff
Brandon Simmons

...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.

Alexander Altman
Collaborator

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

Michael Sloan
Collaborator
Michael Sloan
Collaborator

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

Michael Sloan mgsloan closed this September 02, 2012
Edward Kmett
Owner

@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.

Brandon Simmons
Edward Kmett
Owner

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 !).

Brandon Simmons

@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.

Jonathan Fischoff
Collaborator
Edward Kmett
Owner

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.] =)

Edward Kmett
Owner

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. =)

Michael Sloan
Collaborator
Edward Kmett
Owner

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

Edward Kmett
Owner

@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.

Brandon Simmons

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!

Edward Kmett
Owner

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
Something went wrong with that request. Please try again.