3

Let's say, I have an object with two fields:

data Example = Example { _position :: Int
                       , _storage  :: [Int]}

how do I construct a lens that focuses on position element inside storage?

Also, will it be possible to restrict position values being modified via lenses to a range based on storage size?

It seems like alongside could be used somehow, since Example is isomorphic to a tuple, but I can't comprehend the way to do that.

I'm not sure how to phrase the question, so I was unable to find much relevant info.

sukhmel
  • 1,402
  • 16
  • 29
  • What's your use-case? Modulo index-bounds errors `Example` looks like a variation on the [Store comonad](https://hackage.haskell.org/package/comonad-5/docs/Control-Comonad-Trans-Store.html). I wonder whether that machinery might be a better fit for what you need – Benjamin Hodgson Apr 12 '16 at 12:36
  • My actual use-case does not require bound check, but does require to use some data stored in object to construct a lens. But as restrain was also interesting case, I will take a look at Store, thank you. – sukhmel Apr 12 '16 at 13:23

3 Answers3

5

Edit: I misunderstood the problem, original answer follows at the end.

I don't know any combinator that does what you want, so I wrote one.

(^>>=) :: Lens' s a -> (a -> Lens' s b) -> Lens' s b
--        Lens' s a -> (a -> (b -> f b) -> s -> f s) -> (b -> f b) -> s -> f s
-- (That previous line disregards a forall and the Functor constraints)
(x ^>>= f) btofb s = f (s ^. x) btofb s

Leaving out the type signature and asking ghci for it should give us the most general one, so here goes:

:t (^>>=)
Getting a s a -> (a -> t1 -> s -> t) -> t1 -> s -> t

Getting's doc: "When you see this in a type signature it indicates that you can pass the function a Lens, Getter, Traversal, Fold, Prism, Iso, or one of the indexed variants, and it will just "do the right thing"."

The right side is similarly general, allowing Traversals/Prisms/etc..

Note that this only produces lawful lenslikes if the pointer is not to itself.

Now to apply this combinator - the composition you wanted is:

position ^>>= \p -> storage . ix p

This comes out to be a Traversal, see the original answer.

Or, using another combinator I like:

let (f .: g) x = f . g x in position ^>>= (storage .: ix)

Any with some infix declarations you could even get rid of those brackets.


(This original answer assumes position :: Int locally shadows the position lens.)

We do not know whether the list has a value at that position, so this is not a Lens', but a Traversal', which stands for "traversing over any number of values" rather than "lensing onto one value".

storage . ix position :: Traversal' Example Int

(^?) will return the first value traversed over if any, and thus this term will give you the Int if that position is valid, or Nothing if it isn't.

(^? storage . ix position) :: Example -> Maybe Int

This partial version of that will assume that the position is valid and crash if it isn't.

(^?! storage . ix position) :: Example -> Int

(%~), which applies the function on the right to everything traversed over on the left, works not only for Lenses but all Traversals. (Every Lens is a Traversal by clever ekmett-trickery, and can be inserted anywhere a Traversal can go.)

storage . ix position %~ (+1) :: Example -> Example

And if you absolutely must work with a Lens, any of these partial terms will crash if you try to apply them at invalid positions.

singular $ storage . ix position :: Lens' Example Int
storage . singular (ix position) :: Lens' Example Int

PS: Your record looks like you might want zippers instead: If you only ever move forward/backward incrementally, you'd do less smelly (!!) stuff if you keep track of the list of values to the left of your current position, the value at your current position, and the list of values to the right of your current position, rather than the list of all values and your position in it. For more lensy fun, check out Control.Lens.Zipper, but those are optimized to gracefully nest multiple levels of zippering.

Gurkenglas
  • 2,317
  • 9
  • 17
  • when I write `storage . ix position` I get a type error, namely `Couldn't match type 'Int' with '(Int -> f0 Int) -> Example -> f0 Example' Expected type: Control.Lens.At.Index [Int] Actual type: (Int -> f0 Int) -> Example -> f0 Example In the first argument of 'ix', namely 'position' In the second argument of '(.)', namely 'ix position'` – sukhmel Apr 12 '16 at 14:58
  • Shoot, I thought you shadowed position with a local variable of type Int that already pointed to the correct position in the list. I now understand the problem. Hmm. – Gurkenglas Apr 12 '16 at 15:11
  • I have found this http://stackoverflow.com/questions/21255541/how-do-you-write-a-complex-lens-that-depend-on-other-lenses-using-the-lens-libra question to deal with same problem (although with a twist), now I am trying to create lens with `lens get set`, but to no avail yet. – sukhmel Apr 12 '16 at 15:20
  • wow, that `^>>=` is so cryptic to me, but that seems to be really what I though of -- like an additional socket to insert object into. I will try playing around with that, thank you. – sukhmel Apr 12 '16 at 21:02
2

It seems, that easiest way to achieve this is to write getter and setter using lenses, and then compose a lens:

at_position :: Functor f => (Int -> f Int) -> Example -> f Example
at_position = lens get set
    where
        get :: Example -> Int
        get e = fromJust $ e ^? storage . ix (e^.position)

        set :: Example -> Int -> Example
        set e v = e & storage . ix (e^.position) .~ v

although this maybe could improved, but the code is clear enough, and is not restricted to object structure.

sukhmel
  • 1,402
  • 16
  • 29
0

I think prisms should be better for this situation as pos could be an integer not larger than your list list long, or negative.

I think you could use something like the docu for prisms provide

nat :: Prism' Integer Natural
nat = prism toInteger $ \ i ->
   if i < 0
   then Left i
   else Right (fromInteger i)
storageAtPos Prism' Example Int
storageAtPos = prism $ aux
  where aux (Example p s) | p < 0 || p >= length s = Nothing
                          | otherwise = Just (s !! p)

note: i did not run this code - just made an analogue to the docu (have no ghc right now)

UPDATE

Maybe something like

storageAtPos = \p -> (p^.storage)^?(ix $ p^.pos)

works, but again - I do not have ghc right now to test - as @Gurkenglas pointed out this is no Prism

epsilonhalbe
  • 15,637
  • 5
  • 46
  • 74
  • main point was to create setter/getter without peeking into object I will focus lens on. Prism will allow to modify/view values, but is it really inevitable to pattern match `Example p s`, or is it possible to achieve that using only lens? This is main problem at hand: creating lens (or prism) that is not tightly bound to exact implementation, by combining lenses. – sukhmel Apr 12 '16 at 13:32
  • Prisms are Traversals that traverse over up to one value and that if they do traverse over a value, traverse over all available information. We traverse neither over the position part of the record, nor the rest of the storage, so this is not a Prism. – Gurkenglas Apr 12 '16 at 14:41