Reputation: 6793
What's a succinct way to implement the ensureRecAFlag
function using lenses?
recaFlag=True
then do nothingrecaName="internal_code"
then set recaFlag=True
for that RecArecaName="id"
then set recaFlag=True
for that RecAensureRecAFlag :: 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)
Upvotes: 0
Views: 77
Reputation: 152707
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.
Upvotes: 1