简体   繁体   中英

How do I write an instance for Data.Data ignoring some field?

I have following datatype:

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

I want to be able to call over template (+1) on it to change all Int fields in this data type and down the road. However, I can't request a to be an instance of Data (its constructors are hidden). At the same time, I don't really interested in changing it with over template call, so I tried to write an instance Data (My a) manually, making it ignore first field of type a , but failed. The following code

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]

produces following error:

• 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)))

Is this possible at all? What other options do I have, except writing all the boilerplate by hand?

You can probably get away with:

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]

which allows for:

> 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

See below for a variation to use if the offending type a isn't the first constructor parameter.

Note that gunfold barely matters. See fromConstr and fromConstrB for how it's used. Here, the undefined is no worse than what fromConstr already does.

What really matters is gfoldl . (Note that even though it's not required in the minimal definition, the default implementation is useless for a data type with internal structure that you actually want to operate on, so you're supposed to define it yourself for all non-trivial data structures.) Here, I've just written it so that the a parameter is passed through the fold, allowing the rest to be processed by the k function.

If the a type appears as something other than the first constructor parameter or you have multiple such types that you want to pass through untouched, you'll need something more general, like:

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]

giving:

> gmapT (mkT ((+1) :: Int -> Int)) $ Constr 10 (123 :: Int) "thing" [1,2,3] 3.1415
Constr 11 123 "thing" [1,2,3] 3.1415

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM