Как мне написать экземпляр для Data.Data, игнорируя какое-либо поле? - PullRequest
0 голосов
/ 18 июня 2020

У меня следующий тип данных:

data My a = Constr a Int String Float VariousComplexTypes ...

Я хочу иметь возможность вызвать over template (+1) на нем, чтобы изменить все поля Int в этом типе данных и в дальнейшем. Однако я не могу запросить, чтобы a был экземпляром Data (его конструкторы скрыты). В то же время меня не особо интересует его изменение с помощью вызова over template, поэтому я попытался написать instance Data (My a) вручную, заставив игнорировать первое поле типа a, но не смог. Следующий код

instance Data (My a) where
  gunfold k z _ = k (k (k (z Constr)))
  toConstr _ = con
  dataTypeOf _ = ty

con = mkConstr ty "Constr" [] Prefix
ty = mkDataType "Mod.My" [con]

вызывает следующую ошибку:

• No instance for (Data a) arising from a use of ‘k’
• In the first argument of ‘k’, namely ‘(k (z Constr))’
  In the first argument of ‘k’, namely ‘(k (k (z Constr)))’
  In the expression: k (k (k (z Constr)))
   |
61 |   gunfold k z _ = k (k (k (z Constr)))

Возможно ли это вообще? Какие еще у меня есть варианты, кроме написания всего шаблона вручную?

1 Ответ

2 голосов
/ 18 июня 2020

Вероятно, вам сойдет с рук:

instance Typeable a => Data (My a) where
  gfoldl k z (Constr a x1 x2 x3)
    = z (Constr a) `k` x1 `k` x2 `k` x3
  gunfold k z _ = k . k . k . z $ Constr undefined
  toConstr _ = con
  dataTypeOf _ = ty
con = mkConstr ty "Constr" [] Prefix
ty = mkDataType "Mod.My" [con]

, что позволяет:

> gmapT (mkT ((+1) :: Int -> Int)) $ Constr ["list","of","strings"] 10 "foo" 18.0
Constr ["list","of","strings"] 11 "foo" 18.0
> gmapT (mkT ((+1) :: Int -> Int)) $ Constr (123 :: Int) 10 "foo" 18.0
Constr 123 11 "foo" 18.0    -- note: 123 passes through

См. Ниже вариант, который можно использовать, если тип нарушения a не является первый параметр конструктора.

Обратите внимание, что gunfold практически не имеет значения. См. fromConstr и fromConstrB, чтобы узнать, как это используется. Здесь undefined не хуже того, что уже делает fromConstr.

Что действительно важно, так это gfoldl. (Обратите внимание, что даже если это не требуется в минимальном определении, реализация по умолчанию бесполезна для типа данных с внутренней структурой, с которой вы действительно хотите работать, поэтому вы должны определить ее самостоятельно для всех нетривиальных структур данных. ) Здесь я только что написал так, чтобы параметр a передавался через свертку, позволяя обработать остальное функцией k.

Если тип a выглядит как что-то кроме первого параметра конструктора или у вас есть несколько таких типов, которые вы хотите передать нетронутыми, вам понадобится что-то более общее, например:

data My a b = Constr Int a String b Double deriving (Show)
instance (Typeable a, Typeable b) => Data (My a b) where
  gfoldl k z (Constr x1 a x2 b x3)
    = z go `k` x1 `k` x2 `k` x3
    where go y1 y2 y3 = Constr y1 a y2 b y3
  gunfold k z _ = k . k . k . z $ go
    where go y1 y2 y3 = Constr y1 undefined y2 undefined y3
  toConstr _ = con
  dataTypeOf _ = ty
con = mkConstr ty "Constr" [] Prefix
ty = mkDataType "Mod.My" [con]

, что дает:

> gmapT (mkT ((+1) :: Int -> Int)) $ Constr 10 (123 :: Int) "thing" [1,2,3] 3.1415
Constr 11 123 "thing" [1,2,3] 3.1415
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...