使用镜头初始化嵌套字段的方便方法



我有一些数据类型,它与普通树非常相似,只是一些特殊的形式。

data NestedTree = NT
{ _dummy :: Int
, _tree  :: HashMap String NestedTree
} deriving (Show)
makeLenses ''NestedTree

我想强制使用镜头初始化我的数据类型实例。这是我现在得到的:

example :: NestedTree
example = flip execState (NT 0 mempty) $ do
dummy .= 3
tree.at "foo" ?= flip execState (NT 0 mempty) (dummy .= 10)

在这个例子中,您可以观察到我可以用(NT 3 mempty)替换第一个(NT 0 mempty),但这不是重点。我想要的也是能够使用这种漂亮的命令式样式初始化嵌套的HashMap。更确切地说,我希望能够写这样的东西:

example :: NestedTree
example = flip execState (NT 0 mempty) $ do
dummy .= 3
tree.at "foo" ?= flip execState (NT 0 mempty) $ do
dummy .= 10
tree.at "foo nested" ?= NT 5 mempty
tree.at "bar" ?= flip execState (NT 0 mempty) $ do
dummy .= 15
tree.at "bar nested" ?= NT (-3) mempty

我的真实数据结构更复杂,很快就变得非常难看,只使用简单的记录来初始化它。因此,我想使用某种DSL,镜片非常适合我的需要。但是您可以注意到上面的代码并没有编译。

这是因为($)的优先级最低,而我不能只写tree.at "foo" ?= flip execState (NT 0 mempty) $ do。但我真的不想在嵌套的dos周围添加()

有没有什么好的方法可以将任意运算符与$do混合来编写这样的函数?我真的不想介绍一些像wordsAssign = (?=)这样的助手和像这样的调用函数

wordsAssign (tree.at "foo") $ flip execState (NT 0 mempty) $ do

因为我喜欢?=运算符。也许我做错了所有的事情,而我想做的这种事情可以在没有镜头的情况下用一些手写的操作员来完成?

zoom是为处理嵌套状态更新而量身定制的。不幸的是,在您的情况下,Maybe的性质使使用它有点尴尬:

example :: NestedTree
example = flip execState (NT 0 mempty) $ do
dummy .= 3
zoom (tree.at "foo") $ do
put (Just (NT 0 mempty))
_Just.dummy .= 10
_Just.tree.at "foo nested" ?= NT 5 mempty
-- Or, using zoom one more time:
zoom (tree.at "bar") $ do
put (Just (NT 0 mempty))
zoom _Just $ do
dummy .= 15
tree.at "bar nested" ?= NT (-3) mempty

为了便于比较,如果您不需要在外部级别插入新密钥,则可以使用ix而不是at,并删除所有与Maybe相关的样板:

zoom (tree.ix "foo") $ do
dummy .= 10
tree.at "foo nested" ?= NT 5 mempty

您可以定义自己的?=,其优先级与$相同,以使它们在一起玩得很好:

import Control.Lens hiding ((?=))
import qualified Control.Lens as L
(?=)
:: MonadState s m
=> ASetter s s a (Maybe b) -> b -> m ()
(?=) = (L.?=)
infixr 0 ?=

有了这个,你的例子就奏效了。

最新更新