【问题标题】:How do I write an instance for Data.Data ignoring some field?如何编写忽略某些字段的 Data.Data 实例?
【发布时间】:2020-07-05 19:17:28
【问题描述】:

我有以下数据类型:

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

这可能吗?除了手工编写所有样板文件外,我还有什么其他选择?

【问题讨论】:

  • 向我们展示您失败的实例,以及您从中得到的确切错误。
  • @DanielWagner 完成。不过,不确定以前版本的问题有什么不清楚的地方。
  • @arrowd,该错误大大提高了问题的可发现性(当某些事情无法正常工作时,谷歌搜索错误消息!)。这也像一个校验和,我们对正在发生的事情的假设是正确的。我们也有足够多的人基本上要求我们的志愿者为他们做功课,因此包括所有相关细节以使其成为高质量的问题是诚意的标志。
  • @luqui 成为会员 9 年,我知道所有这些事情并同意它们。但是,这个特定的No instance for (Data a) 错误可能由于各种原因而发生,所以正是为了便于发现,我在问题标题中写了instanceData.Dataignorefield。我仍然相信我的问题没问题,不值得投反对票(在编辑后甚至没有被撤回)。

标签: haskell


【解决方案1】:

你可能会逃脱:

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 几乎不重要。请参阅 fromConstrfromConstrB 了解它的使用方式。在这里,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

【讨论】:

  • 这确实有效!所以,undefined 是要走的路。不过,我仍然不明白它是如何工作的。非常感谢您的回答和gfoldl 注意。
  • 稍后我遇到了我想忽略的a 不是第一个构造函数的参数的情况。感谢您在回答中涵盖此案例。如果可以的话,我会投票两次甚至十几次!
猜你喜欢
  • 1970-01-01
  • 2017-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-26
  • 2017-04-01
  • 2016-06-15
  • 2019-10-19
相关资源
最近更新 更多