7

我正在努力使用该lens库来解决特定问题。我试图通过

  1. 更新的数据结构
  2. 聚焦于更新结构的一部分

到另一个函数,g. 我同时传递了镜头和数据结构,因为g需要数据结构中的一些共享信息以及一条信息。(如果有帮助,数据结构包含有关联合概率分布的信息,但g仅适用于任一边缘,并且需要知道我正在查看哪个边缘。两个边缘之间的唯一区别是它们与其余定义的平均值在数据结构中共享)。

我的第一次尝试看起来像这样

f :: Functor f => Params -> ((Double -> f Double) -> Params -> f Params) -> a
f p l = g (l %~ upd $ p) l
  where upd = ...

g p x = go p p^.x

但是在编译过程中失败了,因为f被推断为Identity用于更新和Const Double获取器。

完成我想做的事情的最佳方法是什么?我可以想象能够执行以下操作之一:

  1. 制作镜头的副本,以便在每种情况下类型推断都不同
  2. 我没有传递更新的结构和镜头,而是传递了原始结构和一个返回修改值的镜头(如果我只想更新镜头所看到的结构部分)。
  3. 为我的函数/数据结构做出更好的设计选择
  4. 完全不同的东西

谢谢你的帮助!

4

3 回答 3

10

András Kovács 的回答展示了如何使用RankNTypes. 如果你想避免RankNTypes,那么你可以使用ALensand cloneLens

f :: a -> ALens' a Int -> (Int, a)
f a l = (newvalue, a & cloneLens l .~ newvalue)
  where oldvalue = a^.cloneLens l
        newvalue = if oldvalue == 0 then 0 else oldvalue - 1

Control.Lens.Loupe提供了可以ALens代替Lens.

请注意,在许多情况下,您还应该能够使用<<%~,这就像%~但也返回旧值,或者<%~,它返回新值:

f :: a -> LensLike' ((,) Int) a Int -> (Int, a)
f a l = a & l <%~ g
  where g oldvalue = if oldvalue == 0 then 0 else oldvalue - 1

这样做的好处是它也可以使用Isos或有时也可以使用Traversals(当目标类型是 a 时Monoid)。

于 2014-05-02T13:18:47.170 回答
7

您希望您的类型签名如下所示:

f :: Params -> Lens Params Params Double Double -> ...
-- alternatively, instead of the long Lens form you can write
-- Lens' Params Double

这不等同于您在签名中写出的内容,因为函子参数在内部被量化Lens

type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)

正确的签名转换为:

f :: Params -> (forall f. Functor f => (Double -> f Double) -> Params -> f Params) -> ...

这样可以防止编译器统一f不同镜头用途的不同参数,即可以多态地使用镜头。请注意,您需要 RankNTypes 或 Rank2Types GHC 扩展才能写出签名。

于 2014-05-02T10:31:55.797 回答
2

Benno 给出了最好的通用答案。

但是,为了完整起见,我在此提供了另外两个选项。

1.)

中有几个Loupe组合器Lens

http://hackage.haskell.org/package/lens-4.1.2/docs/Control-Lens-Loupe.html

他们都有涉及的名字#

^#并且#%=两者都采用ALens了在特定的具体函子选择处实例化的镜头。

如果您需要传递镜头列表,或者您确实需要多次传递,这将很有用。

2.)

另一种选择,也是我更喜欢的策略,是弄清楚如何同时进行这两项操作。

在这里您正在修改,但想要您刚刚设置的值。好吧,是的,可以通过使用<%~而不是%~.

现在您只需在一种函子选择上实例化镜头,您的代码就会变得更快。

于 2014-05-02T17:45:53.003 回答