Reputation: 3083
Suppose I have a data type defined as below:
data Register = Register { _reg_h :: Word8
, _reg_l :: Word8
}
makeLenses ''Register
Now if I want to define a lens that focus from a Register
to a Word16
. This function should be some like below:
refer :: Lens' Register Word16
refer = do h <- reg_h
l <- reg_l
return $ (fromEnum h `shiftL` 8) .&. fromEnum l
(Word8
, Word16
, shiftL
, and .&.
are from Data.Word
and Data.Bits
, and I have enabled RankNTypes
in the beginning of my source code.)
However this code simply doesn't work. I guess it might because the full Lens
type definition has four type parameters and that would be more complex than a simple Lens'
to act as a simple Monad
.
So in what way can I achieve the effect described above?
Thanks.
Upvotes: 0
Views: 344
Reputation: 1667
You cannot define a lens with do notation like that. A lens needs to contain both the notion about how to get a Word16 from a Register, and how to set a Word16 back inn. Your code only says how to get a Word16 from a Register. So it should rather be written as a straight function:
registerToWord16 :: Register -> Word16
registerToWord16 r = (fromEnum (view reg_h r) `shiftL` 8) .&. fromEnum (view reg_l r)
If you just want to get a word16 from a register, you can use this function with
to
from Control.Lens.Getter, to get a Getter you can compose with your lenses.
If you want to be able to go the other way too so that you can set a Register from a Word16, you probably want to write an Iso
. To write an Iso you need to define a function that goes the other way:
word16ToRegister :: Word16 -> Register
word16ToRegister = ...
You can now use the iso :: (a -> b) -> (b -> a) -> Iso' a b
function to create your reference.
refer :: Iso' Register Word16
refer = iso registerToWord16 word16ToRegister
An Iso is higher in the lens hierarchy than Lens, so if you compose it with a Lens you will get a Lens.
Edit:
So you clarified in the comment that you want a register to contain more fields. In that case you want a Lens. You can write it manually like András showed in his post, or you can use the lens :: (s -> a) -> (s -> a -> s) -> Lens' s a
function to construct it from a getter and setter like this:
refer :: Lens' Register Word16
refer = lens get set
where
get :: Register -> Word16
get = registerToWord16
set :: Register -> Word16 -> Register
set reg w16 = reg & reg_h .~ word16ToRegH w16
& reg_l .~ word16ToRegL w16
word16ToRegH :: Word16 -> Word8
word16ToRegL :: Word16 -> Word8
Upvotes: 3
Reputation: 30113
For an idiomatic solution using the lens
library you should look at Reite's answer. Here I give more of a direct answer and show how to implement the lens you intended.
The definition of the Lens
type synonym is Lens s t a b = Functor f => (a -> f b) -> s -> f t
. Making a custom lens can be achieved by implementing a function with this type (specialized to some s t a b
).
Generally, you implement a lens by finding some value of type a
inside the context of type s
, applying the a -> f b
function to it, then wrapping the rest of the context around the resulting f b
using fmap
. A simple example:
-- we'd like to focus on the first element of a pair.
-- we change the type of the first elem from a to a'.
-- thus the type of the whole pair changes from (a, b) to (a', b)
_1 :: Lens (a, b) (a', b) a a'
_1 f (a, b) = fmap (\a' -> (a', b)) (f a)
| |
| ---> apply f to the focus
|
--> wrap the result back
We can implement the lens for Register
analogously:
refer :: Lens' Register Word16
refer f (Register h l) = fmap wrap (f val) where
val = fromIntegral (shiftL h 8) .&. fromIntegral l
wrap n = Register (fromIntegral $ shiftR n 8) (fromIntegral $ 0xffff .&. n)
Again, we apply f
to the value we want to focus on, then wrap the result back. The definition is only complicated slightly by the need to merge and split the numbers.
Upvotes: 3