我试图在this other question的第15章中解决与"Haskell Programming from First Principles"相同的练习。我已经制作了一个Semigroup实例,而且我在编写QuickCheck部分练习时遇到了麻烦。
Semigroup实例应满足:
a <> (b <> c) == (a <> b) <> c
其中<>
是Semigroup mappend。
我想出了以下内容:
import Data.Semigroup
import Test.QuickCheck
semigroupAssoc :: (Eq m, Semigroup m) => m -> m -> m -> Bool
semigroupAssoc a b c = (a <> (b <> c)) == ((a <> b) <> c)
newtype Combine a b = Combine { unCombine :: (a -> b) }
instance Semigroup b => Semigroup (Combine a b) where
(Combine f) <> (Combine g) = Combine (\x -> (f x) <> (g x))
instance CoArbitrary (Combine a b) where
coarbitrary (Combine f) = variant 0
instance (CoArbitrary a, Arbitrary b) => Arbitrary (Combine a b) where
arbitrary = do
f <- arbitrary
return $ Combine f
type CombineAssoc a b = Combine a b -> Combine a b -> Combine a b -> Bool
main :: IO ()
main = do
quickCheck (semigroupAssoc :: CombineAssoc Int Bool)
所有内容都会编译,但quickCheck
行除外,它会抱怨No instance for (Eq (Combine Int Bool)) arising from a use of ‘semigroupAssoc’
。
我认为没有办法测试两个任意函数是否相等(由Combine
包裹的函数),但是练习文本表明这样的事情是可能的。
关于如何使这项工作的任何想法?
编辑:
作者给出了这个练习的暗示:
@ Li-yao Xia的答案似乎是最好的答案。但我不应该使用这个CoArbitrary实例吗?提示:此函数最终将应用于单个值 类型a。但是你将拥有多个可以产生的功能 b型的值。我们如何组合多个值,以便我们拥有 一个b?这个可能会很棘手!记得那个 Combine中的值的类型是函数的值。如果你 无法弄清楚CoArbitrary,不用担心QuickChecking 这个。
答案 0 :(得分:8)
你不能决定两个函数是否相等。但你可以测试它!
当且仅当对于任何输入它们给出相同的输出时,两个函数是相等的。这是一个可测试的属性:生成一些输入,比较输出。如果他们不同,你就会得到一个反例。
-- Test.QuickCheck.(===) requires (Eq b, Show b)
-- but you can use (==) if you prefer.
funEquality :: (Arbitrary a, Show a, Eq b, Show b) => Combine a b -> Combine a b -> Property
funEquality (Combine f) (Combine g) =
property $ \a -> f a === g a
请注意,Bool
会导致&#34;可判定的等式&#34; (==) :: X -> X -> Bool
被Property
替换为我们可能称之为&#34;可测试的平等&#34; funEquality :: X -> X -> Property
。实际上没有必要使用property
并将函数a -> Property
(或a -> Bool
如果使用(==)
)转换为Property
,但类型看起来更整洁。
我们需要重写与associativity属性相对应的函数,因为我们不再依赖Eq
。
type CombineAssoc a b = Combine a b -> Combine a b -> Combine a b -> Property
combineAssoc :: (Arbitrary a, Show a, Eq b, Show b) => CombineAssoc a b
combineAssoc f g h = ((f <> g) <> h) `funEquality` (f <> (g <> h))
修改:此时我们实际上仍然缺少Show
的{{1}}个实例。 QuickCheck提供了一个包装器Combine
来生成和显示函数作为反例。
Fun
答案 1 :(得分:2)
确实不可能或至少不可行,但是你真的不需要像Int
这样大的参数类型的测试用例!
对于较小的类型,例如Int16
,您可以详尽地尝试所有可能的参数来确定平等。 universe package有一个方便的类:
import Data.Universe
instance (Universe a, Eq b) => Eq (Combine a b) where
Combine f == Combine g = all (\x -> f x == g x) universe
然后你的原始支票会起作用,虽然速度慢得令人无法接受;我建议将其更改为quickCheck (semigroupAssoc :: CombineAssoc Int16 Bool)
。