如何将两个简单镜头的结果合二为一

时间:2014-10-04 21:01:39

标签: haskell lenses

假设我的数据类型定义如下:

data Register = Register { _reg_h :: Word8
                         , _reg_l :: Word8
                         }
makeLenses ''Register

现在,如果我想定义一个从RegisterWord16的镜头。这个功能应该如下所示:

refer :: Lens' Register Word16
refer = do h <- reg_h
           l <- reg_l
           return $ (fromEnum h `shiftL` 8) .&. fromEnum l

Word8Word16shiftL.&.来自Data.WordData.Bits,我已启用{{1}在我的源代码的开头。)

然而,这段代码根本不起作用。我想这可能是因为完整的RankNTypes类型定义有四个类型参数,而且比简单的Lens更复杂,可以作为一个简单的Lens'

那么我能以什么方式达到上述效果?

感谢。

2 个答案:

答案 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.Getterto来获取一个吸气剂,你可以用你的镜片进行构图。

如果您希望能够采用其他方式,以便可以从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应用于我们想要关注的值,然后将结果包装回来。由于需要合并和拆分数字,这个定义只会略​​微复杂化。