假设我的数据类型定义如下:
data Register = Register { _reg_h :: Word8
, _reg_l :: Word8
}
makeLenses ''Register
现在,如果我想定义一个从Register
到Word16
的镜头。这个功能应该如下所示:
refer :: Lens' Register Word16
refer = do h <- reg_h
l <- reg_l
return $ (fromEnum h `shiftL` 8) .&. fromEnum l
(Word8
,Word16
,shiftL
和.&.
来自Data.Word
和Data.Bits
,我已启用{{1}在我的源代码的开头。)
然而,这段代码根本不起作用。我想这可能是因为完整的RankNTypes
类型定义有四个类型参数,而且比简单的Lens
更复杂,可以作为一个简单的Lens'
。
那么我能以什么方式达到上述效果?
感谢。
答案 0 :(得分:3)
你不能用这样的符号来定义镜头。镜头需要包含关于如何从寄存器获取Word16的概念,以及如何设置Word16返回栈。您的代码仅说明如何从注册表中获取Word16。所以它应该写成一个直接的函数:
registerToWord16 :: Register -> Word16
registerToWord16 r = (fromEnum (view reg_h r) `shiftL` 8) .&. fromEnum (view reg_l r)
如果您只想从寄存器中获取word16,可以使用此功能
来自Control.Lens.Getter的to
来获取一个吸气剂,你可以用你的镜片进行构图。
如果您希望能够采用其他方式,以便可以从Word16设置注册,则可能需要编写Iso
。要编写Iso,您需要定义另一种方式的函数:
word16ToRegister :: Word16 -> Register
word16ToRegister = ...
您现在可以使用iso :: (a -> b) -> (b -> a) -> Iso' a b
功能创建参考。
refer :: Iso' Register Word16
refer = iso registerToWord16 word16ToRegister
lens hierarchy中的Iso高于镜头,因此如果您使用镜头构图,则会获得镜头。
编辑:
因此,您在评论中澄清了您希望寄存器包含更多字段。在这种情况下,你想要一个镜头。您可以像András在帖子中显示的那样手动编写它,或者您可以使用lens :: (s -> a) -> (s -> a -> s) -> Lens' s a
函数从这样的getter和setter构造它:
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
答案 1 :(得分:3)
对于使用lens
库的惯用解决方案,您应该查看Reite的答案。在这里,我给出了更多的直接答案,并展示了如何实现您想要的镜头。
Lens
类型同义词的定义为Lens s t a b = Functor f => (a -> f b) -> s -> f t
。通过实现这种类型的功能(专用于某些s t a b
),可以实现定制镜头的制作。
通常,您通过在类型a
的上下文中查找类型s
的某些值来实现镜头,将a -> f b
函数应用于它,然后包围其余的上下文结果f b
使用fmap
。一个简单的例子:
-- 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
我们可以类似地为Register
实施镜头:
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)
同样,我们将f
应用于我们想要关注的值,然后将结果包装回来。由于需要合并和拆分数字,这个定义只会略微复杂化。