2010-04-13 87 views
13

让我们考虑一个数据类型有许多构造函数:“模式匹配”代数数据类型的构造函数

data T = Alpha Int | Beta Int | Gamma Int Int | Delta Int 

我想编写一个函数来检查是否有相同的构造产生了两个值:

sameK (Alpha _) (Alpha _) = True 
sameK (Beta _) (Beta _) = True 
sameK (Gamma _ _) (Gamma _ _) = True 
sameK _ _ = False 

维护sameK没有多少乐趣,也不容易检查正确性。例如,当新构造函数被添加到T时,很容易忘记更新sameK。我省略了一行举例:

-- it’s easy to forget: 
-- sameK (Delta _) (Delta _) = True 

问题是如何避免样板sameK?或者如何确保它检查所有T构造函数?


我发现的解决方法是使用单独的数据类型的每个构造的,推导Data.Typeable,并声明一个普通型类,但我不喜欢这样的解决方案,因为它是非常少可读和否则只是一个简单的代数类型的工作对我来说:

{-# LANGUAGE DeriveDataTypeable #-} 

import Data.Typeable 

class Tlike t where 
    value :: t -> t 
    value = id 

data Alpha = Alpha Int deriving Typeable 
data Beta = Beta Int deriving Typeable 
data Gamma = Gamma Int Int deriving Typeable 
data Delta = Delta Int deriving Typeable 

instance Tlike Alpha 
instance Tlike Beta 
instance Tlike Gamma 
instance Tlike Delta 

sameK :: (Tlike t, Typeable t, Tlike t', Typeable t') => t -> t' -> Bool 
sameK a b = typeOf a == typeOf b 
+0

谢谢大家回答。你所有的答案都很有用。 – sastanin 2010-04-14 08:59:03

回答

10

看看Data.Data模块,特别是toConstr函数。随着{-# LANGUAGE DeriveDataTypeable #-},这将为您提供一个1线解决方案,适用于任何类型的实例Data.Data。你不需要弄清楚所有的SYB!

如果由于某种原因(与Hugs卡住?),这不是一个选项,那么这是一个非常丑陋,非常缓慢的黑客攻击。它只适用于您的数据类型为Show(例如,通过使用deriving (Show)--例如内部没有函数类型)。

constrT :: T -> String 
constrT = head . words . show 
sameK x y = constrT x == constrT y 

constrT通过显示它,切碎它分成字,然后让所述第一获取一个T值的最外构造的字符串表示。我给出了一个明确的类型签名,所以你不想在其他类型上使用它(并避免单态限制)。

一些显着的缺点:

  • 这可怕的破坏,当你的类型有缀构造函数(如data T2 = Eta Int | T2 :^: T2
  • 如果一些构造都有一个共同的前缀,这会变慢,因为必须比较大部分的字符串。
  • 不适用于具有自定义show的类型,例如许多库类型。

这就是说,它哈斯克尔98 ......但这就是唯一的好处,我可以说一下吧!

+1

+1 for'toConstr' – 2010-04-13 14:46:09

+1

哇!这只是魔术。非常感谢! – sastanin 2010-04-13 16:33:39

14

另一种可能的方式:

sameK x y = f x == f y 
    where f (Alpha _) = 0 
     f (Beta _) = 1 
     f (Gamma _ _) = 2 
     -- runtime error when Delta value encountered 

运行时错误是不理想,但比默默地给错误的答案更好。

+3

我喜欢它。带有'-Wall'的GHC会在'f'的定义中报告不明确的模式匹配,因此可以避免运行时错误。谢谢你的想法。 – sastanin 2010-04-13 11:21:17

10

您需要使用一个泛型库,如Scrap Your Boilerplate或uniplate来完成此操作。

如果你不想这么重的手,你可以使用戴夫韩丁的解决方案,连同空记录的快捷方式:

... 
where f (Alpha {}) = 0 
     f (Beta {}) = 1 
     f (Gamma {}) = 2 

所以你不必知道每个多少ARGS构造函数有。但它显然还有一些不足之处。

+2

使用'{}'的好方法。我不知道这件事。谢谢。 – sastanin 2010-04-13 11:22:15

+2

记录大括号的绑定比其他任何事都强,所以在技术上你不需要括号。 'f Alpha {} = 0'会正常工作,虽然我不确定它有多可读,它看起来像'f'有两个参数。我有时涉及'f Alpha {} = 0' ... – 2010-04-14 08:12:02

1

您绝对可以使用泛型来消除样板。你的代码是一本教科书的例子,为什么我(和许多其他从不使用顶级通配符_)。尽管写出所有这些情况是件繁琐的事情,但与处理这些错误相比,这并不麻烦。

在这个快乐的例子中,我不仅会使用Dave Hinton的解决方案,还会在辅助函数f上打一个INLINE杂注。

+0

是的,这就是我问的原因。谢谢你的建议。 – sastanin 2010-04-14 08:58:09

相关问题