英文:
How to conditionally set values in a nested record + list using lenses?
问题
实现 ensureRecAFlag
函数的简洁方法是使用 lenses 吗?
- 如果 RecB 包含具有
recaFlag=True
的 RecA,则不执行任何操作。 - 否则,如果 RecB 包含具有
recaName="internal_code"
的 RecA,则为该 RecA 设置recaFlag=True
。 - 否则,如果 RecB 包含具有
recaName="id"
的 RecA,则为该 RecA 设置recaFlag=True
。 - 否则,不执行任何操作。
ensureRecAFlag :: RecB -> RecB
ensureRecAFlag = _todo
data RecA = RecA
{ recaName :: !Text
, recaValue :: !Int
, recaFlag :: !Bool
}
$(makeLensesWith abbreviatedFields ''RecA)
data RecB = RecB
{ recbName :: !Text
, recbRecAList :: ![RecA]
}
$(makeLensesWith abbreviatedFields ''RecB)
英文:
What's a succinct way to implement the ensureRecAFlag
function using lenses?
- If RecB contains a RecA with
recaFlag=True
then do nothing - Else, if RecB contains a RecA with
recaName="internal_code"
then setrecaFlag=True
for that RecA - Else, if RecB contains a RecA with
recaName="id"
then setrecaFlag=True
for that RecA - Else, do nothing
ensureRecAFlag :: RecB -> RecB
ensureRecAFlag = _todo
data RecA = RecA
{ recaName :: !Text
, recaValue :: !Int
, recaFlag :: !Bool
}
$(makeLensesWith abbreviatedFields ''RecA)
data RecB = RecB
{ recbName :: !Text
, recbRecAList :: ![RecA]
}
$(makeLensesWith abbreviatedFields ''RecB)
答案1
得分: 1
我建议你创建一个捕获变更层次结构的幺半群。
data FlagEnsured
= HadFlagAlready
| InternalCode [RecA] {- 旧值 -} [RecA] {- 更新后的值 -}
| Identified [RecA] {- 旧值 -} [RecA] {- 更新后的值 -}
| NotEnsured [RecA]
unchanged :: FlagEnsured -> [RecA]
unchanged = \case
InternalCode old _ -> old
Identified old _ -> old
NotEnsured old -> old
instance Monoid FlagEnsured where mempty = NotEnsured []
instance Semigroup FlagEnsured where
HadFlagAlready <> _ = HadFlagAlready
_ <> HadFlagAlready = HadFlagAlready
-- 如果两个记录都有 internal_code,应该发生什么?
-- 在这里,我假设只有第一个应该更改
InternalCode old new <> fe = InternalCode (old <> unchanged fe) (new <> unchanged fe)
fe <> InternalCode old new = InternalCode (unchanged fe <> old) (unchanged fe <> new)
-- 对于多次命中的情况,同样的问题
Identified old new <> fe = Identified (old <> unchanged fe) (new <> unchanged fe)
fe <> Identified old new = Identified (unchanged fe <> old) (unchanged fe <> new)
NotEnsured old <> NotEnsured old' = NotEnsured (old <> old')
现在你可以独立检查记录,并将它们的修改形式注入到这个类型中。
```haskell
ensureRecAFlagSingle :: RecA -> FlagEnsured
ensureRecAFlagSingle reca
| recaFlag reca = HadFlagAlready
| recaName reca == "internal_code" = InternalCode [reca] [reca'] -- 这里的单引号应该是正常的引号
| recaName reca == "id" = Identified [reca] [reca']
| otherwise = NotEnsured [reca]
where reca' = reca { recaFlag = True }
现在你的顶层函数很简单了。
ensureRecAFlag :: RecB -> RecB
ensureRecAFlag recb = case foldMap ensureRecAFlagSingle (recbRecAList recb) of
HadFlagAlready -> recb
InternalCode _ new -> recb { recbRecAList = new }
Identified _ new -> recb { recbRecAList = new }
NotEnsured _ -> recb
这个解决方案没有使用 lens,但具有一些良好的特性:它只对列表进行一次遍历;它只使用初学者级别的 Haskell 特性,因此阅读和更新要求更改时(尽管不一定容易)都比较直观;它的结构使得在没有任何更改时返回传递的确切对象比返回新分配的副本更加方便。
英文:
I propose that you make a monoid that captures your change hierarchy.
data FlagEnsured
= HadFlagAlready
| InternalCode [RecA] {- old value -} [RecA] {- updated value -}
| Identified [RecA] {- old value -} [RecA] {- updated value -}
| NotEnsured [RecA]
unchanged :: FlagEnsured -> [RecA]
unchanged = \case
InternalCode old _ -> old
Identified old _ -> old
NotEnsured old -> old
instance Monoid FlagEnsured where mempty = NotEnsured []
instance Semigroup FlagEnsured where
HadFlagAlready <> _ = HadFlagAlready
_ <> HadFlagAlready = HadFlagAlready
-- what should happen if two records both had internal_code?
-- here I assume only the first should change
InternalCode old new <> fe = InternalCode (old <> unchanged fe) (new <> unchanged fe)
fe <> InternalCode old new = InternalCode (unchanged fe <> old) (unchanged fe <> new)
-- same question about multiple hits
Identified old new <> fe = Identified (old <> unchanged fe) (new <> unchanged fe)
fe <> Identified old new = Identified (unchanged fe <> old) (unchanged fe <> new)
NotEnsured old <> NotEnsured old' = NotEnsured (old <> old')
Now you can inspect records independently and inject their modified forms into this type.
ensureRecAFlagSingle :: RecA -> FlagEnsured
ensureRecAFlagSingle reca
| recaFlag reca = HadFlagAlready
| recaName reca == "internal_code" = InternalCode [reca] [reca']
| recaName reca == "id" = Identified [reca] [reca']
| otherwise = NotEnsured [reca]
where reca' = reca { recaFlag = True }
Your top-level function is now straightforward.
ensureRecAFlag :: RecB -> RecB
ensureRecAFlag recb = case foldMap ensureRecAFlagSingle (recbRecAList recb) of
HadFlagAlready -> recb
InternalCode _ new -> recb { recbRecAList = new }
Identified _ new -> recb { recbRecAList = new }
NotEnsured _ -> recb
This solution has no lenses, but it does have some nice properties: it does just one traversal of the list; it uses only beginner-level Haskell features so it is straightforward to read and update as requirements change (if not necessarily easy); and it is structured in a way that makes it convenient to return the exact object passed when nothing changes rather than a newly-allocated copy.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论