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

Potentially viable lens-core? #647

Open ekmett opened 8 years ago

ekmett commented 8 years ago

Let's look at subsets of functionality provided by the lens package:

Set 1: Adds Lenses and Traversals. You don't need any dependencies. This could include the machinery to produce the lenses via TH.

Set 2 (contains Set 1): Adds Getters and Folds: You need to add a contravariant dependency.

Set 3 (contains Set 2): Adds Prisms and Isomorphisms: You need to incur a profunctors dependency. (And a bifunctors dependency for Reviews)

Set 4 (requires Set 2): Indexed lenses and traversals, which requires profunctors, so you might as well include indexed getters and folds.

Set 5 (includes Set 4) The ad hoc classes for things like At and Ix and Wrapped. This drags in all the dependencies. Orphans are not a valid solution. A lot of folks would like to be able to supply these instances but without a lens dependency, but that just doesn't fly.

Set 6: Template Haskell for generating lenses and traversals. However today these also generate isomorphisms in the right situations, so while this could be done badly with just a dependency on Set 1, this really incurs a dependency on Set 3 merging it with...

Set 7: Template Haskell for generating isomorphisms and prisms.

Set 8: The rest: There are a few smaller things for things like generics, uniplate, exceptions, etc. that only incur a couple of dependencies.

Set{1..4,6..7} could go into a package that only had a few small dependencies and if I had to pick a fault-line to cut at that would probably be the one, since including sets 1..3 means sets 6..7 are basically free for a template-haskell dependency and the end-user experience is much improved, since that is the stuff they are reaching for as often as not.

Set 4 on the other hand is THE reason why packages like wreq use lens not another lens-like package. It increases the internal size of the package but not the dependency set, if set 3 is already involved.

It should in theory be possible to cleave off a lens-core that included Set{1..4,6..7} and most of the current combinators, in a way that drastically reduced the dependencies of the package. Unfortunately, this leaves a massive interface between lens and such a lens-core package, and would require a lot of engineering work to do.

Is it worth it?

ekmett commented 8 years ago

Actually, it looks like the indexed machinery may be a pain point. It requires a bunch of extra dependencies due to conjoined. So a logical subset would be Set{1..3,6..7}.

Unfortunately that means that there'd STILL be 3 packages in the end if we split out the indexed stuff, and the classes for TraverseWithIndex, etc. would still not be in such a smaller package =(

mgsloan commented 8 years ago

I agree that {1..3,6-7} could be quite a nice and compact subset. I think if you're reaching for the indexed stuff, then a dependency on lens is fine

No need to necessarily have a huge interface. How about exporting just the basic functions for defining and using lenses / prisms / isos / traversals / getters / folds. Then, this package can also have the dual purpose of being a more beginner friendly lens package.

ekmett commented 8 years ago

@mgsloan If a combinator we provide today can be provided without incurring an extra set of dependencies I'd rather include it rather than try to figure out some half-assed difficulty rubrik to test everything on. Including it gives a hard and fast rule and could proceed apace without involving a committee for every line of code. Trying to pick out some magic "suggested" set just runs into style variations between users and gives no objective right answer.

I'll admit I'm somewhat uncomfortable about the fact that it wouldn't be able to easily supply the Indexed machinery, since that means that such a 'core' package wouldn't be able to be used for packages like wreq that take advantage of that power today.

It'd add additional pressure on the wrong side between doing the right thing and doing the thing with fewer imports. Right now once you opt into lens you might as well provide indices, etc. if they make sense.

ekmett commented 8 years ago

Let's see what the extra dependencies are for Set 4.

class
  ( Choice p, Corepresentable p, Comonad (Corep p), Traversable (Corep p)
  , Strong p, Representable p, Monad (Rep p), MonadFix (Rep p), Distributive (Rep p)
  , Costrong p, ArrowLoop p, ArrowApply p, ArrowChoice p, Closed p
  ) => Conjoined p where

is the major source of difficulty.

Comonad and Distributive only really incur an extra comonad and distributive dependency.

I'd assumed that there was a Representable dependency from adjunctions on the representation of the profunctor as well.

ekmett commented 8 years ago

comonad comes in transitively through bifunctors so that isn't an extra cost.

ekmett commented 8 years ago

But then bifunctors already brings in template-haskell so sets 6..7 aren't an additional dependency cost over set 3.

ekmett commented 8 years ago

And profunctors already depends on distributive.

ekmett commented 8 years ago

So there is no cost to including set 4 from conjoined.

mgsloan commented 8 years ago

Not bad at all! Seem to me {1..4,6-7} is a good subset indeed.

If a combinator we provide today can be provided without incurring an extra set of dependencies I'd rather include it rather than try to figure out some half-assed difficulty rubrik to test everything on.

True. I think the main appeal of lens-core would be to export prisms / isos from packages. The dual purpose of being more beginner friendly is.

So how about "Is it frequently used to define a member of the menagerie?" to determine inclusion? Then, if someone wants to make a easy-lens package with minimal combinators, they are free to base it on this core package.

ekmett commented 8 years ago

I still think I'd rather stick to "affects imports" as a test.

A lot of things like partsOf seem like they are arbitrary right up until you realize you can't live without them and sure as hell can't reimplement it on the spot!

ekmett commented 8 years ago

So let's turn to downsides:

snoyberg commented 8 years ago

The use case I had for this request was much more minimal than what's being discussed right now. My issue is that libraries I maintain (examples: http-client, warp) often need to expose some way of configuring a value. There are three basic ways I can do that:

Record syntax, as we all know, doesn't compose nicely, and is frankly an eye sore. Generated haddocks for these fields can be confusing too if you don't export data constructors. Providing getters and setters fixes some of this, but (1) doubles the API, and (2) still doesn't compose nicely. So I'd like to use lenses.

The issue I'm having: if I switch warp and http-client to a set of lenses right now, I'd end up with a few unsatisfactory results:

So my request is really minimal: a new package with just the functions view, set, and over, with their operator versions. AFAICT, these can be provided with only depending on mtl (and even that's probably not needed, I think just transformers would do it). If this happened, I could import this package from all of my packages without being lynched, export them from my packages, non-users of lens would get no conflicts, and users of lens would be happier because more packages in the ecosystem support lens.

The only casualties I see in this scenario are:

All of the other bits of functionality that could come along are nice, but referencing the lynch bit again, even adding in distributive would probably be a deal-breaker for my use case.

Said another way: I'm hoping by having a standard, minimal-depedency package that everyone agrees on for this basic functionality, we can get the general Haskell ecosystem to more widely embrace lenses as a replacement for record syntax.

snoyberg commented 8 years ago

Just to give you the heads-up as early as possible to avoid wasting more of your time: it looks like my use case in http-client is not going to be well served by lens after all (unless you see something we missed). Relevant discussion is at:

https://twitter.com/snoyberg/status/720522453945094144

I'd still say that providing a lens-core would be a good thing, and I'd consider exposing a lens-based API for other things in the future. But most likely this specific case will end up with a getter/setter API instead.

ekmett commented 8 years ago

Getting Iso and Prism included would transitively include distributive through profunctors, and one of my most common requests has been a common core that allows them, too.

At this point in time, it sounds like the core that we'd accept factoring out would already be too big for you, which has been the issue with almost every request we've had along these lines.

ekmett commented 8 years ago

Similarly Set{1..2} alone isn't enough to allow the template-haskell for lens generation (despite transitively incurring a template-haskell dependency!) which is one of the other most common requests.

Zemyla commented 8 years ago

Now that Data.Bifunctor is in base, you don't need template-haskell for it anymore, which alleviates a lot of the pain involved. (Also, I checked, none of the typeclasses require Bifoldable or Bitraversable, which aren't in base yet.)

This means lens-core can be {1-4} plus whatever parts of 8 are doable while just in base.