Question

I’m making a lens combinator in Ruby and I can’t figure out what the generally accepted name of it is. The unknown function composes two lenses that have the same source type and their target type (using Benjamin C. Pierce’s terminology) is a hash map. The unknown function takes in those two lenses and returns a new lens which has the same source type and target type as the original lenses.

It looks like this (Ruby syntax):

lens_a.get(source)
> {:title => "some title"}
lens_b.get(source)
> {:description => "some description"}
new_lens = unknown_function(lens_a, lens_b)
new_lens.get(source)
> {:title => "some title", :description => "some description"}

A diagram of the combinator I’m trying to build can be seen on slide 18 of this presentation (the slide’s title is “Merge?”).

I’ve looked at Haskell's lens docs (small parts of which I can understand), but I can’t figure out which combinator this is.

What is the standard name for the unknown_function above? If this lens doesn’t have a standard name, are there a few standard functions that can be composed to make it? If not, I’ll probably just call it merge.

Était-ce utile?

La solution

I believe that roughly the right idea for your combinator, in Haskell, is

merge :: Lens' s t -> Lens' s t -> Lens' s (t, t)

possibly generalized to

merge :: Lens' s t -> Lens' s t' -> Lens' s (t, t')

so that your two targets may differ in type. We could "implement" this as follows, but doing so will reveal a problem

merge l1 l2 = lens pull push where
  pull s          = (view l1 s, view l2 s)
  push s (t1, t2) = set l2 t2 (set l1 t1 s)

In particular, on the Setter side of this equation we explicitly order the way we put back our values. This means that we can use merge to easily build lenses which violate the lens laws. In particular, they violate the PUT-GET law. Here's a counterexample

newtype Only a = Only a

-- A lens focused on the only slot in Only
only :: Lens' (Only a) a
only inj (Only a) = Only <$> inj a

-- We merge this lens with *itself*
testLens :: Lens' (Only a) (a, a)
testLens = merge only only

-- and now it violates the lens laws
> view testLens (Only 1 & testLens .~ (1,2))
(2,2)

Or, in plain english, if we merge a lens with itself then the Setter side turns into two sequential sets on the same location. This means that if we try to set it with a pair of values which are different only the second set will persist and thus we violate the PUT-GET law.

The lens library tends to eschew invalid lenses so this combinator isn't available. The closest we have is alongside which has the following (restricted) type

alongside :: Lens'  s     a
          -> Lens'  s'    a'
          -> Lens' (s,s') (a,a')

but, as you can see, this ensures that our source is also a product type so that the Setters apply uniquely to each side of the source. If we were to try to write a dup Lens which we could compose with alongside to build merge we'll run into the same problem as before

dup  :: Lens' a (a, a) -- there are two possible implementations, neither are lenses
dup1 inj a = fst <$> inj a
dup2 inj a = snd <$> inj a

Now, all of this might be fairly technical and meaningless from a Ruby perspective. In Ruby you're not going to have compiler enforced type safety and so you'll likely blend a lot of the rules together and achieve less strictness. In such a case, it might be meaningful to implement merge with its sequential writes semantics.

In fact, based on the examples you've given, it looks like the counterexample I pointed out cannot even happen in Ruby since your target type is a Hash and ensures unique keys already.

But it's important to note that merge can be used to build improper lenses if you look at it hard enough. Fundamentally what that means is that in more complex scenarios, using merge can easily lead to weird bugs caused by it violating our intuitive sense for what a lens is. This may not be a huge problem for Ruby as complex abstraction is frowned upon in the Ruby community anyway, but that's why we have the laws.

Autres conseils

Seems to be like you want to combine two lenses into a traversal. I don't see that combinator anywhere, but I'm new to lens so I'm not sure what it would be called anyway.

Something like:

compoundEye :: Traversable tl => tl (Lens s t a b) -> Traversal s t a b

But, the hoogle results are not promising.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top