创建多态镜头 [英] Creating polymorphic lens
问题描述
我可以通过以下操作为我的数据类型中的最后一个字段( c
)创建镜头:
{ - #LANGUAGE DuplicateRecordFields# - }
data X1 ac = X1 {a':: a,b':: Int,c': :c}
data X2 abc = X2 {a':: a,b':: b,c':: c}
类HavingFieldC x cs ct其中
c :: Functor f => (cs - > f ct) - > x cs - > f(x ct)
实例HavingFieldC(X1 a)cs ct其中
c =镜头
(\ X1 {c'} - > c')
(\ X1 {..} v - > X1 {c'= v,..})
实例HavingFieldC(X2 ab)cs ct其中
c =镜头
(\ X2 {c'} - > c')
(\X2 {..} v - > X2 {c'= v,..})
对于字段 a
和 b
您可以推广 HavingField
class;特别是可以使用函数依赖关系来表达更新的类型变量和记录类型之间的关系。这允许更新类型变量出现在任何位置;它允许更新单态字段。
class FieldC k k'x x'| k x' - > k',k'x - > k,k - > x,k' - > x'其中
fieldC :: Functor f => (x - > f x') - > k - > f k'
实例(b〜b 0,b'〜b 0')=> FieldC(X1a b)(X1a b')b0 b0'其中...
实例(b〜b0,b'〜b0')=> FieldC(X2 cab)(X2 ca b')b0 b0'其中...
您可以定义实例以同样的方式;注意一些等式约束被放置在上下文中以改进类型推断。您可以将上面的第一个实例读为实例FieldC(X1 ab)(X1 a b')b b'
。
其他字段的类以完全相同的方式定义;这实际上是定义透镜类最常用的方法(如果注意到 fieldC
的类型实际上只是 Lens
class FieldA k k'x x'| k k'x x'
)。 k x' - > k',k'x - > k,k - > x,k' - > x'其中
fieldA :: Functor f => (x - > f x') - > k - > f k'
class FieldB k k'x x'| k x' - > k',k'x - > k,k - > x,k' - > x'其中
fieldB :: Functor f => (x - > f x') - > k - > (注意:这也可以推广到一个类,其中一个额外的参数对应于字段名;这可能超出了这个问题的范围)。
现在应该更清楚如何编写实例声明:
instance(x0〜Int,x1〜Int)=> FieldB(X1a c)(X1a c)x0 x1其中...
实例(b0〜b,b0'〜b')=> FieldB(X2 a b c)(X2 a b'c)b0 b0'其中...
实例(a〜a0,a'〜a0')=> FieldA(X1a c)(X1a'c)a0a0'其中...
实例(a0〜a,a0'〜a')=> FieldA(X2 abc)(X2 a'bc)a0 a0'where ...
唯一的区别对于单形字段,字段类型是单形类型。
一个简单的测试将显示正确的多态类型被赋值:
foo x =
let y = view fieldB x
set fieldA(2 * y)$ set fieldC(3 + y)x
您可以在特定的实例中询问GHCi的推断类型:
\x - > foo x`asTypeOf` X1 {} :: X1 a b - > X1 Int Int
\x - > foo x`asTypeOf` X2 {} :: Num a0'=> X2 a a0'b - > X2 a0'a0'a0'
这种一般模式可以被发现实现,例如 here 。这个实现稍微宽松一点;
Functor f =>中的
是一个类型类参数,而不是被普遍量化。根据您的具体使用情况,这可能或可能不适合您。f
..I am able to create a lens for the last field (
c
) in my data types by doing the follow:{-# LANGUAGE DuplicateRecordFields #-} data X1 a c = X1 { a' :: a, b' :: Int, c' :: c } data X2 a b c = X2 { a' :: a, b' :: b, c' :: c } class HavingFieldC x cs ct where c :: Functor f => (cs -> f ct) -> x cs -> f (x ct) instance HavingFieldC (X1 a) cs ct where c = lens (\X1 { c' } -> c') (\X1 {..} v -> X1 {c' = v, ..}) instance HavingFieldC (X2 a b) cs ct where c = lens (\X2 { c' } -> c') (\X2 {..} v -> X2 {c' = v, ..})
Is there something similar I can do for fields
a
andb
解决方案You can generalize the definition of the
HavingField
class; in particular, you can express the relationship between the updated type variable and the record type using functional dependencies. This allows the update type variable to occur in any position; and it allows updates to monomorphic fields.class FieldC k k' x x' | k x' -> k', k' x -> k, k -> x, k' -> x' where fieldC :: Functor f => (x -> f x') -> k -> f k' instance (b ~ b0, b' ~ b0') => FieldC (X1 a b) (X1 a b') b0 b0' where ... instance (b ~ b0, b' ~ b0') => FieldC (X2 c a b) (X2 c a b') b0 b0' where ...
You define instances in much the same way; note that some of the equality constraints are placed in the context to improve type inference. You can read the first instance above as
instance FieldC (X1 a b) (X1 a b') b b'
.The classes for other fields are defined in exactly the same way; this is essentially the most general way to define a class for lenses (which should be more apparent if one notes that the type of
fieldC
is actually justLens k k' x x'
).class FieldA k k' x x' | k x' -> k', k' x -> k, k -> x, k' -> x' where fieldA :: Functor f => (x -> f x') -> k -> f k' class FieldB k k' x x' | k x' -> k', k' x -> k, k -> x, k' -> x' where fieldB :: Functor f => (x -> f x') -> k -> f k'
(Note: this too can be generalized to a single class with an additional parameter corresponding to the field name; this is probably outside the scope of this question).
Now it should be clearer how to write the instance declarations:
instance (x0 ~ Int, x1 ~ Int) => FieldB (X1 a c) (X1 a c) x0 x1 where ... instance (b0 ~ b, b0' ~ b') => FieldB (X2 a b c) (X2 a b' c) b0 b0' where ... instance (a ~ a0, a' ~ a0') => FieldA (X1 a c) (X1 a' c) a0 a0' where ... instance (a0 ~ a, a0' ~ a') => FieldA (X2 a b c) (X2 a' b c) a0 a0' where ...
The only difference for monomorphic fields being that the field types are monomorphic types.
A simple test will show that the proper polymorphic types are assigned:
foo x = let y = view fieldB x in set fieldA (2 * y) $ set fieldC (3 + y) x
You can ask GHCi for the inferred types at particular instantiations:
\x -> foo x `asTypeOf` X1{} :: X1 a b -> X1 Int Int \x -> foo x `asTypeOf` X2{} :: Num a0' => X2 a a0' b -> X2 a0' a0' a0'
This general pattern can be found implemented e.g. here. This implementation is slightly more permissive; the
f
inFunctor f => ..
is a typeclass parameter instead of being universally quantified over. Depending on your specific use cases, this may or may not work for you.这篇关于创建多态镜头的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!