Создание полиморфной линзы

Я могу создать линзу для последнего поля (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?


person Vanson Samuel    schedule 13.11.2017    source источник


Ответы (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'

Этот общий шаблон можно найти реализованным, например. здесь. Эта реализация немного более либеральна; f в Functor f => .. является параметром класса типов, а не универсальной количественной оценкой. В зависимости от ваших конкретных вариантов использования это может работать или не работать для вас.

person user2407038    schedule 13.11.2017
comment
Что более ограничительно, чем что? Как? Я что-то упускаю. - person dfeuer; 14.11.2017
comment
@dfeuer Я не уверен, что я там написал .. надеюсь, редактирование имеет больше смысла. Спасибо за то, что вы такой внимательный читатель! - person user2407038; 14.11.2017
comment
Нужны ли для этого UndecidableInstances? - person Vanson Samuel; 14.11.2017
comment
@VansonSamuel Я так думаю (чтобы иметь ограничения равенства типов в контексте - с более простыми версиями, то есть instance FieldC (X1 a b) (X1 a b') b b', вам это не нужно). - person user2407038; 14.11.2017
comment
@user2407038 user2407038 Я опубликовал еще один вопрос о том, как сделать упомянутое вами обобщение: field-name-into-one-typeclass" title="как упростить вызов поля полиморфного имени поля в один typeclass"> stackoverflow.com/questions/47401763/. Хотелось бы увидеть продолжение вашего ответа. - person Vanson Samuel; 21.11.2017