循环镜头,并使用每个镜头和设置

jfl*_*fla 2 haskell haskell-lens

这是我一直在处理的一些代码的版本,删除了细节.希望很清楚我正在尝试做什么,但如果没有,我可以澄清.我很擅长使用镜头,所涉及的复杂类型往往使它们看起来比它们的价值更麻烦.

-- some data type
type AType = ...

data Thing = Th { _v, _w, _x :: AType, _otherStuff :: ... }

makeLenses ''Thing

-- some operation that can be performed on corresponding x or w or v values in two Things.
f :: AType -> AType -> AType
f = ...

-- apply f to the corresponding v, and w, and x values in a and b; then store each result in the respective field of a, leaving other fields untouched.
transform :: Thing -> Thing -> Thing
transform a b =
    let transformVorWorX :: Lens' Thing AType -> AType
        transformVorWorX lens = 
            let vwx  = view lens a
                vwx' = view lens b
            in  f vwx vwx'

    in  foldl (\a' lens -> set lens (transformVorWorX lens) a') a [v,w,x]
Run Code Online (Sandbox Code Playgroud)

当我编译时,ghc吐出来

Could not deduce (f ~ Identity)
from the context (Functor f)
  bound by a type expected by the context:
             Functor f => (AType -> f AType) -> Thing -> f Thing
  at ...
  ‘f’ is a rigid type variable bound by
      a type expected by the context:
        Functor f => (AType -> f AType) -> Thing -> f Thing
      at ...
Expected type: (AType -> f AType) -> Thing -> f Thing
  Actual type: ASetter Thing Thing AType AType
In the first argument of ‘transformVorWorX’, namely ‘lens’
In the second argument of ‘set’, namely ‘(transformVorWorX lens)’
Run Code Online (Sandbox Code Playgroud)

为什么这段代码不起作用?我发现,替换lenscloneLens lens让它编译,但我不知道为什么,这种感觉非常难看-有一个更优雅的方式做我想做的,这也许是更普遍?

非常感谢

lef*_*out 5

Lens' 普遍量化的是在引擎盖下

type Lens' s a = ? f . Functor f => (a -> f a) -> s -> f s
Run Code Online (Sandbox Code Playgroud)

所以类型[v,w,x]必须是

[? f . Functor f => (AType -> f AType) -> Thing -> f Thing]
Run Code Online (Sandbox Code Playgroud)

- 列表中有一个量子.这种类型称为impredicative类型,Haskell不支持它们.(具有该名称的GHC扩展已存在一段时间,但它不起作用.)

有一种相对简单的方法可以解决这个问题:将量子隐藏在a中newtype.在镜头库中,这是ReifiedLens.有了它,你可以写

transform a b =
    let transformVorWorX :: ReifiedLens' Thing AType -> AType
        transformVorWorX (Lens lens) = 
            let vwx  = view lens a
                vwx' = view lens b
            in  f vwx vwx'
    in  foldl (\a' lens -> set (runLens lens) (transformVorWorX lens) a') a
            [Lens v, Lens w, Lens x]
Run Code Online (Sandbox Code Playgroud)

这段代码完全符合您所设想的运行时行为,但它显然非常难看.

你发现自己的替代解决方案也有效地做了同样的事情但是更好.为了看看那里到底发生了什么,我会把cloneLens内部transformVorWorX:

transform a b =
    let transformVorWorX :: ALens' Thing AType -> AType
        transformVorWorX lens = 
            let vwx  = view (cloneLens lens) a
                vwx' = view (cloneLens lens) b
            in  f vwx vwx'
    in  foldl (\a' lens -> set (cloneLens lens) (transformVorWorX lens) a') a [v,w,x]
Run Code Online (Sandbox Code Playgroud)

这样做的原因是,像ReifiedLens- 但不像Lens- ALens'没有量子.它只是一个具体的实例Functor f,因此它不需要impredicativity但你可以直接使用v,wx[ALens' Thing AType]列表中.仿函数实例经过精心挑选,不会失去任何一般性,因此cloneLens能够为您提供完整的,通用量化的镜头.

而不是克隆它,你也可以使用^#运营商,直接意见ALens,并storing代替set:

transform a b =
    let transformVorWorX :: ALens' Thing AType -> AType
        transformVorWorX lens = 
            let vwx  = a^#lens
                vwx' = b^#lens
            in  f vwx vwx'
    in  foldl (\a' lens -> storing lens (transformVorWorX lens) a') a [v,w,x]
Run Code Online (Sandbox Code Playgroud)

如果我们将其浓缩成一个不那么冗长的形式,它看起来相当不错IMO:

transform a b = foldl updater a [v,w,x]
 where updater a' lens = a' & lens #~ f (a^#lens) (b^#lens)
Run Code Online (Sandbox Code Playgroud)

我还建议一个完全不同的解决方案:定义一个访问所有感兴趣值的光学元件,即

vwx :: Traversal' Thing AType
vwx f (Thing ? ? ? otherVals)
    = (\?' ?' ?' -> Thing ?' ?' ?' otherVals)
      <$> f ? <*> f ? <*> f ?
Run Code Online (Sandbox Code Playgroud)

您可以让Template Haskell助手为您自己定义:

makeLensesFor [("_v", "vwx"), ("_w", "vwx"), ("_x", "vwx")] ''Thing
Run Code Online (Sandbox Code Playgroud)

现在,遍历可以用作简单的折叠,从而获得所有值列表.然后,您可以使用标准zip来执行元素的转换:

transform a b = ...
 where tfmedVals = zipWith f (a^..vwx) (b^..vwx)
Run Code Online (Sandbox Code Playgroud)

要将修改后的值列表放回容器中,该库还具有以下功能:partsOf.有了它,你的整个转型归结为

transform a b = a & partsOf vwx .~ zipWith f (a^..vwx) (b^..vwx)
Run Code Online (Sandbox Code Playgroud)