Haskell quickBatch:在mconcat测试ZipList幺半群会导致堆栈溢出
我已经为ZipList半群和幺半群创建了孤立实例。但是,当我在monoid上运行quickBatch的测试时,在mconcat测试中,出现了堆栈溢出错误。如何解决此错误?为什么会有这样的错误?这是因为我不太理解纯mempty,因为我主要是从HaskellBook第17章应用程序第17.8节ZipList幺半群中得到的Haskell quickBatch:在mconcat测试ZipList幺半群会导致堆栈溢出,haskell,Haskell,我已经为ZipList半群和幺半群创建了孤立实例。但是,当我在monoid上运行quickBatch的测试时,在mconcat测试中,出现了堆栈溢出错误。如何解决此错误?为什么会有这样的错误?这是因为我不太理解纯mempty,因为我主要是从HaskellBook第17章应用程序第17.8节ZipList幺半群中得到的 zl :: ZipList (Sum Int) zl = ZipList [1,1 :: Sum Int] instance Semigroup a => Semigr
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
zl::ZipList(Sum Int)
zl=ZipList[1,1::Sum Int]
实例半群a
=>半群(ZipList a),其中
()=liftA2()
实例(等式a,幺半群a)
=>幺半群(ZipList a),其中
mempty=纯mempty
mappend=()
mconcat as=
foldr mappend mempty as
main::IO()
main=do
quickBatch$monoid zl
是的,错误是由于
纯mempty
引起的,但这并不意味着纯mempty
是错误的。让我们先看看那里
查看定义中涉及的类型有很大帮助mempty=pure mempty
:
mempty::ZipList a
mempty=(pure::a->ZipList a)(mempty::a)
基本上,我们将使用pure
操作从a
类型的mempty
创建一个ZipList
。从这里可以看到:
pure::a->ZipList a
纯x=ZipList(重复x)
总之,ZipList a
的mempty
将成为一个ZipList
,包含基础类型a
的mempty
值的无限重复列表
回到你遇到的这个错误。当您尝试在
ZipList(Sum Int)
上运行测试monoid
时,QuickCheck将测试一系列属性
- 前两个检查左标识和右标识属性。它们所做的是生成类型为
的值,并验证x::ZipList(Sum Int)
x mempty=mempty x=x
- 第三个检查对于任何两个值
,我们得到了x,y::ZipList(Sum Int)
mappendx
y=xy
- 第四个检查对于任何值列表
,使用x::[ZipList(Sum Int)]
折叠这些值与使用mappend
折叠它们相同mconcat
任意
实例来生成该类型的值。此外,ZipList a
的arbitral
实例与[a]
的arbitral
实例相同,但随后被包装在ZipList
中。最后,[a]
的任意
实例永远不会产生无限列表(因为在检查是否相等时,这些列表会导致问题,例如进入无限循环或堆栈溢出),因此ZipList(Sum Int)
类型的“任何值”也永远不会是无限的
具体来说,这意味着QuickCheck永远不会任意生成值
mempty::ZipList a
,因为这是一个无限列表
那么为什么前3个会通过,但最后一个会因堆栈溢出而失败呢?在前三个测试中,我们从未尝试过将无限列表与无限列表进行比较。让我们看看为什么不
- 在前两个测试中,我们关注的是
和xmempty==x
。在这两种情况下,memptyx==x
是我们的“任意”值之一,它永远不会是无限的,因此这个等式永远不会进入无限循环x
- 在第三个测试中,我们将生成两个有限的ziplist
和x
以及y
将它们组合在一起。这一切都不是无限的mappend
- 在第三种情况下,我们生成一个ZipLists列表,并启用该列表。但是,如果列表为空,会发生什么?嗯,
,折叠空列表会产生mconcat[]=mempty
。这意味着,如果空列表作为任意输入生成(这是完全可能的),那么测试将尝试确认一个无限列表等于另一个无限列表,这将始终导致堆栈溢出或黑洞mempty
你怎么能解决这个问题?我可以想出两种方法:
ZipList
定义自己版本的EqProp
,以便它仅在列表的某个有限前缀上比较相等。这可能涉及制作一个新类型包装器(可能是newtype MonZipList a=MonZipList(ZipList a)
),派生一组实例,然后手工编写一个EqProp
。这可能会奏效,但有点不雅观
monoid
,使用第四个测试的不同版本。例如,如果您限制它,使测试只使用非空列表,那么您就不会有任何问题。要做到这一点,您应该从以下内容开始。请注意,它当前将“mconcat”属性定义为property mconcatP
where
mconcapt::[a]->属性
mconcatP as=mconcat as=-=foldr mappend mempty as
使用QuickCheck自己的NonEmptyList
类,您可以将其重写为:
mconcapt::NonEmptyList a->Property
mconcatP(非空列表as)=mconcat as=-=foldr mappend mempty as
显然,这是一个稍微弱一点的条件,但至少它不会挂起。
mempty::ZipList a
是一个无限列表:我想知道它是否在尝试做类似mempty mempty
的事情(尽管它会尝试mconcat
而不是mappend
本身)@chepner谢谢你的提示,但是我仍然对pu的用法感到困惑