2

出于我的目的,我需要一个带有某种“属性”的类似地图的容器。我的对象可能具有不同的属性。为了访问这些属性,我决定使用Control.Lens,这很有趣。但是我找不到这种逻辑的类似镜头的方式:在访问属性时,如果它不存在,则添加默认值,然后返回新值。但如果它在那里,只需使用它。

换句话说,prop_test应该返回 True:

type PropertyMap = Map.Map Int String
data Properties = Properties { _propertyMap :: PropertyMap }
  deriving (Eq)

makeLenses ''Properties

emptyProperties = Properties Map.empty

propertyLens pIndex = propertyMap . at pIndex . traverse

property1 = propertyLens 1
property2 = propertyLens 2
property3 = propertyLens 3

obj1Properties :: State Properties ()
obj1Properties = do
    property1 .= "Property1 value"
    property2 .= "Property2 value"

obj2Properties :: State Properties ()
obj2Properties = do
    property1 .= "Property1 value"
    property3 .= "Property3 value"

prop_test = op1 /= emptyProperties
    where
        op1 = execState obj1Properties emptyProperties

但是现在,op1 等于 emptyProperties。对于默认值,我可以使用 Data.Default。我该如何处理?或者我应该使用另一种方法吗?例如,State monad 中的一个包装函数,它为我解包并检查属性是否存在。

另外,您能否提供 Control.Lens(或其他镜头包)的真实示例的链接,好吗?

4

2 回答 2

3

如果你用等于和类似两个地图的联合来创建Properties“Monoid”的实例,那么镜头库就会做正确的事情。memptyemptyPropertiesmappend

另一种方法是使用专为遍历设计的组合器之一(可以返回 0 或更多结果的镜头)然后处理缺失的情况。请参阅文档,该文档(^?)将生成 aMaybe t(^..)将为您提供[t].

于 2013-09-15T06:46:22.403 回答
0

感谢John F. Miller的回答,考虑到这一点,我意识到,我确实需要包装函数来简化属性的使用。因此,代码将是:

type PropertyMap = Map.Map Int String
data Properties = Properties { _propertyMap :: PropertyMap }
  deriving (Eq, Show)

emptyProperties = Properties Map.empty

makeLenses ''Properties

newtype PAccessor = PAccessor { pKey :: Int }

property1 = PAccessor 1
property2 = PAccessor 2
property3 = PAccessor 3

(|=) accessor v = do
    ps <- get
    put $ propertyMap . at (pKey accessor) ?~ v $ ps

setProperty = (|=)

getProperty accessor = do
    ps <- get
    return $ ps ^. propertyMap . ix (pKey accessor)

maybeProperty accessor = do
    ps <- get
    return $ ps ^. propertyMap . at (pKey accessor)

obj1Properties :: State Properties String
obj1Properties = do
    property1 |= "Property1 value"
    property2 |= "Property2 value"
    p1Val <- getProperty property1
    p3Val <- getProperty property3 -- Returns default value (empty string)
    return (p1Val ++ p3Val)

obj2Properties :: State Properties String
obj2Properties = do
    property2 |= "Property2 value"
    property3 |= "Property3 value"
    Nothing <- maybeProperty property1
    Just p2Val <- maybeProperty property2
    return p2Val

expectedProps1 = Properties $ Map.fromList [ (1, "Property1 value")
                                           , (2, "Property2 value") ]

expectedProps2 = Properties $ Map.fromList [ (2, "Property2 value")
                                           , (3, "Property3 value") ]

prop_test1 = (props == expectedProps1) && (val == "Property1 value")
    where
        (val, props) = runState obj1Properties emptyProperties

prop_test2 = (props == expectedProps2) && (val == "Property2 value")
    where
        (val, props) = runState obj2Properties emptyProperties

prop_test3 = val == "Property2 value"
    where
        val = evalState obj2Properties emptyProperties

所有测试功能均通过,即结果等于 True。ix作为奖励,代码显示了和之间的区别at。我会尝试改进这个解决方案,但它现在看起来可以接受。

于 2013-09-15T11:29:02.090 回答