我可以通过以下操作为我的数据类型中的最后一个字段(c
)创建一个镜头:
{-# 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, ..})
我可以为字段a
和b
答案 0 :(得分:1)
您可以概括HavingField
类的定义;特别是,您可以使用函数依赖关系表达更新的类型变量和记录类型之间的关系。这允许更新类型变量出现在任何位置;它允许更新单形字段。
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 ...
您以相同的方式定义实例;请注意,某些等式约束放在上下文中以改进类型推断。您可以将上面的第一个实例读作instance FieldC (X1 a b) (X1 a b') b b'
。
其他字段的类以完全相同的方式定义;这基本上是为镜片定义类的最常用方法(如果有人注意到fieldC
的类型实际上只是Lens 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'
(注意:这也可以推广到单个类,并且附加参数对应于字段名称;这可能超出了本问题的范围。)
现在应该更清楚如何编写实例声明:
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 ...
单态字段的唯一区别是字段类型是单态类型。
一个简单的测试将显示分配了正确的多态类型:
foo x =
let y = view fieldB x
in 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。这种实现稍微宽松一点; f
中的Functor f => ..
是一个类型参数,而不是普遍量化的。根据您的具体使用情况,这可能适用于您,也可能不适合您。