问题
i've created orphaned instances for ZipList Semigroup and Monoid. However, when I run the tests from quickBatch on monoid, at the mconcat test, there is a stack overflow error. How do I resolve this error? Why is there such an error? Is it due to pure mempty, which I do not quite understand as I got this mostly from HaskellBook Chapter 17 Applicative section 17.8 ZipList Monoid?
zl :: ZipList (Sum Int)
zl = ZipList [1,1 :: Sum Int]
instance Semigroup a
=> Semigroup (ZipList a) where
(<>) = liftA2 (<>)
instance (Eq a, Monoid a)
=> Monoid (ZipList a) where
mempty = pure mempty
mappend = (<>)
mconcat as =
foldr mappend mempty as
main :: IO ()
main = do
quickBatch $ monoid zl
回答1:
Yes, the error is due to pure mempty, but that doesn't mean pure mempty is wrong. Let's look there first.
It helps a lot to look at the types involved in the definition mempty = pure mempty:
mempty :: ZipList a
mempty = (pure :: a -> ZipList a) (mempty :: a)
Basically, we're going to use the pure operation to create a ZipList out of the mempty of type a. It helps from here to look at the definition of pure for ZipList:
pure :: a -> ZipList a
pure x = ZipList (repeat x)
In total, mempty for ZipList a is going to be a ZipList containing the infinitely repeating list of mempty values of the underlying type a.
Back to this error you're getting. When you try to run the test monoid over ZipList (Sum Int), QuickCheck is going to test a sequence of properties.
- The first two check the left identity and right identity properties. What these do is generate values of type
x :: ZipList (Sum Int)and verify thatx <> mempty = mempty <> x = x. - The third checks that for any two values
x, y :: ZipList (Sum Int), we have thatxmappendy = x <> y. - The fourth checks that for any list of values
x :: [ZipList (Sum Int)], folding these withmappendis the same asmconcating them.
Before I continue, it's really important to note that when I say "for any value", I really mean that QuickCheck is using the Arbitrary instance of the said type to generate values of that type. Furthermore, the Arbitrary instance for ZipList a is the same as the Arbitrary instance for [a] but then wrapped in ZipList. Lastly, the Arbitrary instance for [a] will never produce an infinite list (because those will cause problems when you're checking for equality, like going into an infinite loop or overflowing the stack), so these "for any values" of type ZipList (Sum Int) will never be infinite either.
Specifically, this means that QuickCheck will never arbitrarily generate the value mempty :: ZipList a because this is an infinite list.
So why do the first 3 pass but the last one fails with a stack overflow? In the first three tests, we never end up trying to compare an infinite list to an infinite list. Let's see why not.
- In the first two tests, we're looking at
x <> mempty == xandmempty <> x == x. In both cases,xis one of our "arbitrary" values, which will never be infinite, so this equality will never go into an infinite loop. - In the third test, we're generating two finite ZipLists
xandyandmappending them together. Nothing about this will be infinite. - In the third case, we're generating a list of ZipLists and
mconcatenating the list. But, what happens if the list is empty? Well,mconcat [] = mempty, and folding an empty list producesmempty. This means, if the empty list is generated as the arbitrary input (which is perfectly possible), then the test will try to confirm that an infinite list is equal to another infinite list, which will always result in a stack overflow or black hole.
How can you fix this? I can come up with two methods:
You can define your own version of
EqPropforZipListso that it only compares equality on some finite prefix of the list. This would likely involve making a newtype wrapper (perhapsnewtype MonZipList a = MonZipList (ZipList a)), deriving a bunch of instances, and then writing anEqPropone by hand. This will probably work but is a little inelegant.You can write your own version of
monoidthat uses a different version of the fourth test. For instance, if you restrict it so that the test only uses non-empty lists, then you won't have any problem. To do this, you should start by looking at the definition of the monoid property tests. Notice that it currently defines the "mconcat" property asproperty mconcatPwhere
mconcatP :: [a] -> Property
mconcatP as = mconcat as =-= foldr mappend mempty as
Using QuickCheck's own NonEmptyList class, you can rewrite this for your purposes as:
mconcatP :: NonEmptyList a -> Property
mconcatP (NonEmptyList as) = mconcat as =-= foldr mappend mempty as
Obviously, this is a slightly weaker condition, but at least it's one that won't hang.
来源:https://stackoverflow.com/questions/65716919/haskell-quickbatch-testing-ziplist-monoid-at-mconcat-results-in-stack-overflow