0

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

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

现在,如果我想定义一个聚焦从 aRegister到 a的镜头Word16。这个函数应该如下所示:

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

( Word8, Word16, shiftL, and.&.来自Data.Wordand Data.Bits, 我RankNTypes在源代码的开头启用了。)

但是,此代码根本不起作用。我想这可能是因为完整的Lens类型定义有四个类型参数,这将比一个简单Lens'的更复杂Monad

那么通过什么方式可以达到上述的效果呢?

谢谢。

4

2 回答 2

3

你不能用这样的符号来定义镜头。一个镜头需要包含关于如何从寄存器中获取 Word16 以及如何设置 Word16 后台客栈的概念。您的代码仅说明如何从寄存器中获取 Word16。所以它应该写成一个直接的函数:

registerToWord16 :: Register -> Word16
registerToWord16 r = (fromEnum (view reg_h r) `shiftL` 8) .&. fromEnum (view reg_l r)

如果您只想从寄存器中获取 word16,您可以将此函数与 toControl.Lens.Getter一起使用,以获得可以用镜头组合的 Getter。

如果您也希望能够采用另一种方式,以便可以从 Word16 设置寄存器,您可能需要编写一个Iso. 要编写 Iso,您需要定义一个相反的函数:

word16ToRegister :: Word16 -> Register
word16ToRegister = ...

您现在可以使用该iso :: (a -> b) -> (b -> a) -> Iso' a b函数来创建您的参考。

refer :: Iso' Register Word16
refer = iso registerToWord16 word16ToRegister

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
于 2014-10-04T21:54:46.653 回答
3

对于使用该lens库的惯用解决方案,您应该查看 Reite 的答案。在这里,我给出了更多直接的答案,并展示了如何实现您想要的镜头。

Lens类型同义词的定义是Lens s t a b = Functor f => (a -> f b) -> s -> f t。可以通过实现这种类型的功能(专门用于某些s t a b)来实现定制镜头。

a通常,您通过在 type的上下文中找到一些 type 的值s,将a -> f b函数应用到它,然后将上下文的其余部分包装在结果f busing周围来实现镜头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我们想要关注的值,然后将结果包装回去。由于需要合并和拆分数字,定义只是稍微复杂了一点。

于 2014-10-04T22:09:56.907 回答