默认值,如果它在map中不存在

时间:2013-09-15 05:57:43

标签: haskell map lens

出于我的目的,我需要一个具有某种类型的“属性”的类似地图的容器。我的对象可能具有不同的属性。为了访问这些属性,我决定使用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(或其他镜头包)的真实世界示例链接吗?

2 个答案:

答案 0 :(得分:3)

如果你使Properties和'Monoid'的实例mempty等于emptyPropertiesmappend,那么像两个地图的联合一样合理,那么镜头库将会做正确的事。

另一种方法是使用专门为穿越而设计的组合器之一(可以返回0或更多结果的镜头)然后处理丢失的情况。请参阅(^?)的文档,该文档会产生Maybe t(^..),它会为您提供[t]

答案 1 :(得分: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。 作为奖励,代码显示ixat之间的差异。我会尝试改进这个解决方案,但现在看起来可以接受。