出于我的目的,我需要一个具有某种类型的“属性”的类似地图的容器。我的对象可能具有不同的属性。为了访问这些属性,我决定使用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(或其他镜头包)的真实世界示例链接吗?
答案 0 :(得分:3)
如果你使Properties
和'Monoid'的实例mempty
等于emptyProperties
和mappend
,那么像两个地图的联合一样合理,那么镜头库将会做正确的事。
另一种方法是使用专门为穿越而设计的组合器之一(可以返回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。
作为奖励,代码显示ix
和at
之间的差异。我会尝试改进这个解决方案,但现在看起来可以接受。