创建多态透镜

Creating polymorphic lens

我可以通过执行以下操作为我的数据类型中的最后一个字段 (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, ..})

我可以对字段 ab

做类似的事情吗

你可以概括HavingFieldclass的定义;特别是,您可以使用函数依赖关系来表达更新类型变量和记录类型之间的关系。这允许更新类型变量出现在任何位置;并且它允许更新单态字段。

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'.

其他字段的classes以完全相同的方式定义;这基本上是为镜头定义 class 的最通用方法(如果注意到 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'

(注意:这也可以概括为单个 class,带有与字段名称对应的附加参数;这可能不在本问题的范围内)。

现在应该更清楚如何编写实例声明了:

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。这个实现稍微宽松一些; Functor f => .. 中的 f 是一个类型 class 参数,而不是被普遍量化。根据您的具体用例,这可能适合您,也可能不适合您。