使用镜头映射记录的两个字段

Using lenses to mappend two fields of a record

我正在努力适应一些基本的 lens 功能。在尝试介绍镜头之前,我从以下类型和功能开始:

import qualified Data.Set as S

data Sets = Sets {pending, placed, open :: S.Set Int}
interesting :: Sets -> [Int]
interesting = toList . pending <> placed

即,我想要挂起节点和放置节点的并集,表示为列表(我后来在列表理解中使用结果,所以集合不方便)。

我的基本问题是:如何使用 lens 中的工具复制它?如果您对该问题有很好的回答,则可以跳过以下内容;是我自己初学探索的记录 space.

我重命名了这些字段以给自己镜片:

{-# LANGUAGE TemplateHaskell #-}
import Control.Lens
import qualified Data.Set as S

data Sets = Sets {_pending, _placed, _open :: S.Set Int}
makeLenses ''Sets

现在希望重新实现 interesting。当然,没有镜头也不难(toList . _pending <> _placed),但我正在努力掌握镜头的窍门,这似乎是一个有用的练习。

我的第一个想法是 pendingplaced 仍然都是函数,我仍然想逐点映射与它们的结果有点相关但实际上并不相关的东西,所以 pending <> placed 至少应该看起来很有趣:

*Main Data.Foldable> :t pending <> placed
pending <> placed
  :: (Semigroup (f Sets), Functor f) =>
     (S.Set Int -> f (S.Set Int)) -> Sets -> f Sets

现在,这是什么类型,我可以对其执行哪些操作?它看起来有点像受约束的 Getter,也许吧,即使我无法通过写 :t pending <> placed :: Getter _s _a 让 GHCI 告诉我约束是什么。我们可以尝试将它传递给 view,它需要一个 Getter,并且有效:

*Main Data.Foldable> :t view (pending <> placed)
view (pending <> placed) :: MonadReader Sets m => m (S.Set Int)

好吧,这是 Sets -> S.Set Int 的概括,我可以用 toList 组合它以​​恢复我必须开始的内容:

*Main Data.Foldable> :t toList . view (pending <> placed)
toList . view (pending <> placed) :: Sets -> [Int]

但这似乎不是很令人满意:这正是我以前所拥有的,但有一个额外的 view 电话,我觉得我在这里没有使用任何镜头的力量。我也不太明白 pending <> placed 在这种情况下的“含义”。

我考虑的另一件事是我想做的很像foldMap,而我有的有点像Getter,所以我应该可以做一些foldMapOf.

*Main Data.Foldable> :t foldMapOf (pending <> placed)
foldMapOf (pending <> placed)
  :: Semigroup r => (S.Set Int -> r) -> Sets -> r

这还需要一个参数,明显的候选者是 toList:

*Main Data.Foldable> :t foldMapOf (pending <> placed) toList
foldMapOf (pending <> placed) toList :: Sets -> [Int]

这有正确的类型,但是语义不同:它在转换为 [Int] 之后使用 <> 而不是在基础 Set Int 上,所以如果 _pending_placed 共享元素,我们在结果中得到重复的副本。

我可以做的另一件事是使用 toListOf (pending <> placed),生成一个集合列表,然后使用普通的非镜头函数将它们混合在一起:

*Main Data.Foldable> :t toList . mconcat . toListOf (pending <> placed)
toList . mconcat . toListOf (pending <> placed) :: Sets -> [Int]

这行得通,但是很丑,而且似乎没有抓住要点。

那么,lenses 有什么更好的工具吗?我是否选择了一个如此简单的问题,以至于我看不到透镜相对于简单的记录场吸气剂的优势?

Have I chosen a problem so simple that I can't see the advantage of lenses over simple record-field getters?

我想说的就是这些。直觉上,pending <> placed 是一个只读目标:没有明智的方法来修改两个集合的并集作为 Sets 结构的一部分,因为它不对应于其中的任何实际内容.这就是为什么你最终得到一个 getter,正如你发现的那样,它本质上是一个函数。

*Main Data.Foldable> :t pending <> placed
pending <> placed
  :: (Semigroup (f Sets), Functor f) =>
     (S.Set Int -> f (S.Set Int)) -> Sets -> f Sets

Now, what is this type, and what operations can I perform on it? It looks sorta like a constrained Getter, maybe, even though I can't get GHCI to tell me what the constraints are by writing :t pending <> placed :: Getter _s _a.

虽然该类型允许其他一些不太相关的东西,但您真正想要的是 f ~ Const (S.Set Int),这使得镜头上的映射实际上映射了检索到的集合。专攻 Const 确实会给你 getter,或者,挑剔的话,a Getting:t 这一点更有帮助:

ghci> :t pending <> placed :: Getting _ _ _

<interactive>:1:34: error:
    • Found type wildcard ‘_’ standing for ‘S.Set Int’
      To use the inferred type, enable PartialTypeSignatures
    • In the third argument of ‘Getting’, namely ‘_’
      In the type ‘Getting _ _ _’
      In an expression type signature: Getting _ _ _

<interactive>:1:32: error:
    • Found type wildcard ‘_’ standing for ‘Sets’
      To use the inferred type, enable PartialTypeSignatures
    • In the second argument of ‘Getting’, namely ‘_’
      In the type ‘Getting _ _ _’
      In an expression type signature: Getting _ _ _

<interactive>:1:30: error:
    • Found type wildcard ‘_’ standing for ‘_’
      Where: ‘_’ is a rigid type variable bound by
               the inferred type of
                 it :: Semigroup _ => Getting _ Sets (S.Set Int)
               at <interactive>:1:1
      To use the inferred type, enable PartialTypeSignatures
    • In the first argument of ‘Getting’, namely ‘_’
      In the type ‘Getting _ _ _’
      In an expression type signature: Getting _ _ _