haskell 如何使用镜头在嵌套记录+列表中有条件地设置值?

7fyelxc5  于 2023-06-23  发布在  其他
关注(0)|答案(1)|浏览(107)

使用镜头实现ensureRecAFlag函数的简洁方法是什么?

  • 如果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)
iezvtpos

iezvtpos1#

我建议您创建一个monoid来捕获您的变更层次结构。

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')

现在,您可以独立地检查记录并将其修改后的表单注入到该类型中。

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

这个解决方案没有透镜,但它确实有一些很好的特性:它只遍历列表一次;它只使用初级的Haskell特性,所以当需求改变时,它可以直接读取和更新(如果不一定容易的话);并且其结构化使得在没有任何变化时返回传递的确切对象而不是新分配的副本变得方便。

相关问题