使用Haskell的lens库,将镜头fmap化

6
在下面的代码中,我的问题涉及最顶层的函数someFunc(下面的所有内容只是为了提供完整的示例)。我在那里使用了记录句法获取器和fmap。使用 lens 实现someFunc的方式是什么?
import           Control.Lens
import           Data.IntMap  (IntMap)

someFunc :: Farm -> IntMap Size
someFunc farm =
  _barnSize <$> farm ^. farmBarns

data Farm = Farm
  { _farmBarns :: IntMap Barn
  }

farmBarns :: Lens' Farm (IntMap Barn)
farmBarns = lens _farmBarns (\farm barns -> farm { _farmBarns = barns } )

type Size = (Int, Int)

data Barn = Barn
  { _barnSize :: Size
  }

barnSize :: Lens' Barn Size
barnSize = lens _barnSize (\barn size -> barn { _barnSize = size } )
2个回答

7

只需将_barnSize替换为(^. barnSize)或等效的view barnSize

someFunc :: Farm -> IntMap Size
someFunc farm = view barnSize <$> farm ^. farmBarns

对于一个“100%镜头”解决方案,您可以使用mapped setter。然而,在这种情况下,我认为这样做没有任何真正的优势。

someFunc :: Farm -> IntMap Size
someFunc farm = (mapped %~ view barnSize) (farm ^. farmBarns)

另一种可能的拼写方式是使用to将所有内容组合在一个getter中。 在这里,这并没有给你带来太多好处,但如果您想通过链接其他getter /折叠等以样式继续使用,那么它可能会有些方便。
someFunc :: Farm -> IntMap Size
someFunc farm = farm ^. farmBarns . to (fmap (view barnSize))

有一个特殊的组合器可以代替上面的to/(^.)组合。它被称为views:

someFunc :: Farm -> IntMap Size
someFunc farm = views farmBarns (fmap (view barnSize)) farm

@ruben.moor 我增加了第四种可能性,虽然我认为它也不比第一种更好。 - duplode

2

您可以使用mapped(或traversed)和镜头一起“映射”遍历,例如:

someFunc :: Farm -> IntMap Size
someFunc farm =
    farm ^. farmBarns & mapped %~ view barnSize

这可能有点令人困惑,但是这里发生的事情是,我会添加括号以使其更清晰。
someFunc :: Farm -> IntMap Size
someFunc farm =
    (farm ^. farmBarns) & (mapped %~ (view barnSize))

基本上,我们使用farm ^. farmBarns 来从农场获取IntMap Barns,右侧的 & 构造一个设置器,使用中缀 %~,它是 over 的另一种写法,但 over 传递给它的函数实际上只是聚焦于使用镜头的barnSize。 view barnSize :: Barn -> Size

最后,我们使用&将其全部连接在一起,相当于flip $,并将右侧的设置器应用于左侧的结果。

(^..) 使得结果为 [Size],而不是 IntMap Size - duplode
我已经修复了解决方案,使用了映射。 - Chris Penner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接