Haskell use first level lenses to create complex lens

拜拜、爱过 提交于 2020-01-23 02:15:07

问题


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.


回答1:


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.




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




回答3:


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



来源:https://stackoverflow.com/questions/36569994/haskell-use-first-level-lenses-to-create-complex-lens

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!