使用镜头两次

dbe*_*ham 7 haskell lenses haskell-lens

我正在努力使用该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
Run Code Online (Sandbox Code Playgroud)

但是在编译过程中失败了,因为它f被推断为Identity更新和Const Doublegetter.

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

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

谢谢你的帮助!

ben*_*ofs 9

AndrásKovács的答案显示了如何实现这一目标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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这具有以下优点:它也可以使用Isos或者有时也使用Traversals(当目标类型是a时Monoid).


And*_*ács 7

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

f :: Params -> Lens Params Params Double Double -> ...
-- alternatively, instead of the long Lens form you can write
-- Lens' Params Double
Run Code Online (Sandbox Code Playgroud)

这与您在签名中写出的内容不同,因为functor参数在内部量化Lens:

type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)
Run Code Online (Sandbox Code Playgroud)

正确的签名转换为:

f :: Params -> (forall f. Functor f => (Double -> f Double) -> Params -> f Params) -> ...
Run Code Online (Sandbox Code Playgroud)

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