这是我一直在处理的一些代码的版本,删除了细节。希望我能清楚地知道自己要做什么,但如果没有,我可以澄清一下。我对使用镜头非常陌生,所涉及的复杂类型经常使它们看起来比它们值得更麻烦。
-- 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
可以让它编译,但我不确定为什么,这感觉非常难看 - 是否有一种更优雅的方式来做我想要的,也许更一般?
非常感谢
答案 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}中直接使用v
,w
和x
列表。仿函数实例被巧妙地选择为不会失去任何一般性,因此[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)