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

时间:2017-09-09 18:47:59

标签: 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]

当我编译时,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)’

为什么这段代码不起作用?我发现用lens替换cloneLens lens可以让它编译,但我不确定为什么,这感觉非常难看 - 是否有一种更优雅的方式来做我想要的,也许更一般?

非常感谢

1 个答案:

答案 0 :(得分:5)

Lens'是全面量化的

type Lens' s a = ∀ f . Functor f => (a -> f a) -> s -> f s

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

[∀ f . Functor f => (AType -> f AType) -> Thing -> f Thing]

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

有一种相对简单的方法可以解决这个问题:将量子隐藏在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]

此代码完全具有您所设想的运行时行为,但它显然非常难看。

您自己发现的替代解决方案有效同样的事情,但相当好。要查看实际发生的情况,我会将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]

这样做的原因是,与ReifiedLens一样 - 但与Lens不同 - ALens'没有量词。它只是Functor f的具体实例,因此它不需要不可信,但您可以在{{1}中直接使用vwx列表。仿函数实例被巧妙地选择为不会失去任何一般性,因此[ALens' Thing AType]能够为您提供完整的,通用量化的镜头。

您也可以使用直接查看cloneLens的{​​{3}}运算符,而不是ALens代替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]

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

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

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

vwx :: Traversal' Thing AType
vwx f (Thing в ш к otherVals)
    = (\в' ш' к' -> Thing в' ш' к' otherVals)
      <$> f в <*> f ш <*> f к

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

makeLensesFor [("_v", "vwx"), ("_w", "vwx"), ("_x", "vwx")] ''Thing

现在,遍历可以用作简单的折叠,因此可以用作所有值的^#。然后,您可以使用标准zip来执行元素的转换:

transform a b = ...
 where tfmedVals = zipWith f (a^..vwx) (b^..vwx)

要将修改后的值列表放回容器中,该库还有一个函数:storing。有了这个,你的整个转型归结为

transform a b = a & partsOf vwx .~ zipWith f (a^..vwx) (b^..vwx)