使用镜头两次

时间:2014-05-02 08:49:27

标签: haskell lens lenses

我正在努力将lens库用于解决特定问题。我正试图通过

  1. 更新的数据结构
  2. 专注于更新结构的一部分的镜头
  3. 到另一个函数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和getter的Const Double

    完成我想做的最好的方法是什么?我可以想象能够做到以下其中一种:

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

3 个答案:

答案 0 :(得分:9)

András Kovács answer显示了如何使用RankNTypes实现此目标。如果您希望避免RankNTypes,那么您可以使用ALenscloneLens

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(当目标类型为Monoid时)。

答案 1 :(得分:7)

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

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

这与您在签名中写出的内容不同,因为functor参数在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扩展名才能写出签名。

答案 2 :(得分:2)

本诺给出了最好的通用答案。

然而,还有另外两个选项,我在这里提供完整性。

1)

Loupe中有几个Lens个组合器。

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

他们都有名称涉及#

^##%=都采用ALens,这是在特定的仿函数选择中实例化的镜头。

如果您需要传递镜片列表,或者您确实需要多次通过,这可能很有用。

2。)

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

您正在修改,但想要设置的值。好吧,是的,可以使用<%~代替%~来为您提供。

现在你只需要在一个仿函数中实例化镜头,你的代码就会变快。