问题
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