在我的项目中,我创建了一个数据类型,它可以包含以下几种类型的值之一:
data PhpValue = VoidValue | IntValue Integer | BoolValue Bool
我现在想做的是,有一种简单的方法来检查PhpValue
类型的两个值是否具有相同的构造函数(如果我对这里的术语感到困惑,请纠正我,但基本上我想检查的是,例如,这两个值是否都是IntValue
,而不关心特定的值)。
下面是我为此编写的函数:
sameConstructor :: PhpValue -> PhpValue -> Bool
sameConstructor VoidValue VoidValue = True
sameConstructor (IntValue _) (IntValue _) = True
sameConstructor (BoolValue _) (BoolValue _) = True
sameConstructor _ _ = False
这可以正常工作,但我真的不喜欢它:如果我添加更多的构造函数(如FloatValue Float
),我将不得不重写函数,并且随着我的数据定义变得更大,它将变得更大。
这个问题:有没有一种方法可以编写这样的函数,这样当我添加更多的构造函数时,它的实现就不会改变?
需要说明的是:我不想更改data
的定义,我的其余代码中已经有足够的Monad了;)
发布于 2012-04-12 03:44:34
看看Data.Data
和它的toConstr
函数。这将返回构造函数的表示形式,可以比较该构造函数是否相等。
通过扩展(您可以将{-# LANGUAGE DeriveDataTypeable #-}
放在模块的顶部),您可以自动派生一个Data
实例:
data PhpValue = VoidValue | IntValue Integer | BoolValue Bool
deriving (Typeable, Data)
然后,您应该能够使用toConstr
函数按构造函数进行比较。
现在,以下情况将为真:
toConstr (BoolValue True) == toConstr (BoolValue False)
使用Data.Function
中的on
,您现在可以将sameConstructor
重写为:
sameConstructor = (==) `on` toConstr
这与
sameConstructor l r = toConstr l == toConstr r
我认为使用on
的版本更容易阅读。
发布于 2012-04-12 03:52:37
这在Haskell和ML系列语言中被称为expression problem;有许多不令人满意的解决方案(包括使用Data.Typeable
和滥用类型类,在Haskell中),但没有很好的解决方案。
发布于 2017-07-30 12:51:57
由于定义遵循常规格式,因此您可以使用模板Haskell为任何数据类型自动派生此类函数。我继续写了一个simple package,因为我对现有的解决方案并不完全满意。
首先,我们定义一个类
class EqC a where
eqConstr :: a -> a -> Bool
default eqConstr :: Data a => a -> a -> Bool
eqConstr = (==) `on` toConstr
然后是一个函数deriveEqC :: Name -> DecsQ
,它将自动为我们生成实例。
default
是一个default signature,这意味着当该类型是Data
的一个实例时,我们可以省略eqConstr
的定义,而回到Tikhon的实现。
模板Haskell的好处是它产生了一个更有效的函数。我们可以编写$(deriveEqC ''PhpValue)
并获得一个实例,这正是我们手动编写的实例。看一下生成的核心:
$fEqCPhpValue_$ceqConstr =
\ ds ds1 ->
case ds of _ {
VoidValue ->
case ds1 of _ {
__DEFAULT -> False;
VoidValue -> True
};
IntValue ds2 ->
case ds1 of _ {
__DEFAULT -> False;
IntValue ds3 -> True
};
BoolValue ds2 ->
case ds1 of _ {
__DEFAULT -> False;
BoolValue ds3 -> True
}
}
相比之下,使用Data
会引入大量额外的间接性,因为在比较它们是否相等之前,会为每个参数实例化一个显式的Constr
:
eqConstrDefault =
\ @ a $dData eta eta1 ->
let {
f
f = toConstr $dData } in
case f eta of _ { Constr ds ds1 ds2 ds3 ds4 ->
case f eta1 of _ { Constr ds5 ds6 ds7 ds8 ds9 ->
$fEqConstr_$c==1 ds ds5
}
}
(在计算toConstr
的过程中还有很多其他不值得展示的臃肿的东西)
在实践中,这导致模板Haskell实现的速度大约是原来的两倍:
benchmarking EqC/TH
time 6.906 ns (6.896 ns .. 6.915 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 6.903 ns (6.891 ns .. 6.919 ns)
std dev 45.20 ps (32.80 ps .. 63.00 ps)
benchmarking EqC/Data
time 14.80 ns (14.77 ns .. 14.82 ns)
1.000 R² (1.000 R² .. 1.000 R²)
mean 14.79 ns (14.77 ns .. 14.81 ns)
std dev 60.17 ps (43.12 ps .. 93.73 ps)
https://stackoverflow.com/questions/10112733
复制相似问题