Haskell quickBatch:mconcatでZipListモノイドをテストするとスタックオーバーフローが発生する
ZipListSemigroupとMonoidの孤立したインスタンスを作成しました。ただし、monoidでquickBatchからテストを実行すると、mconcatテストでスタックオーバーフローエラーが発生します。このエラーを解決するにはどうすればよいですか?なぜそのようなエラーがあるのですか?それが原因でありpure mempty
、私はほとんどHaskellBook章17節のApplicative 17.8 ZipListモノイドからこれを得たように私はかなり理解していないいますか、?
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
回答
はい、エラーはによるものですがpure mempty
、それpure mempty
は間違っているという意味ではありません。最初にそこを見てみましょう。
定義に含まれるタイプを確認するのに大いに役立ちますmempty = pure mempty
。
mempty :: ZipList a
mempty = (pure :: a -> ZipList a) (mempty :: a)
基本的に、このpure
操作を使用して、ZipList
outmempty
型を作成しますa
。ここから、pureforZipListの定義を確認するのに役立ちます。
pure :: a -> ZipList a
pure x = ZipList (repeat x)
合計すると、mempty
forZipList a
は、基になる型の値のZipList
無限に繰り返されるリストを含むことになりmempty
ますa
。
発生しているこのエラーに戻ります。あなたがテストを実行しようとするとmonoid
オーバーZipList (Sum Int)
、QuickCheckは、プロパティの順序をテストするために起こっています。
- 最初の2つは、左側のIDと右側のIDのプロパティを確認します。これらが行うことは、タイプの値を生成し
x :: ZipList (Sum Int)
、それを検証することx <> mempty = mempty <> x = x
です。 - 3番目は、任意の2つの値について
x, y :: ZipList (Sum Int)
、そのx
mappendがあることを確認しy = x <> y
ます。 - 4番目は、値のリストについて
x :: [ZipList (Sum Int)]
、これらを折りたたむことはそれらを折りたたむこととmappend
同じであるmconcat
ことを確認します。
続行する前に、「任意の値に対して」と言うとき、QuickCheckがそのArbitrary
タイプのインスタンスを使用してそのタイプの値を生成していることを意味することに注意することが非常に重要です。さらに、のArbitrary
インスタンスZipList a
はのインスタンスと同じArbitrary
です[a]
が、でラップされZipList
ます。最後に、のArbitrary
インスタンスが[a]
無限リストを生成することはありません(無限ループに入ったり、スタックをオーバーフローしたりするなど、等しいかどうかをチェックするときに問題が発生するため)。したがって、これらの「任意の値」タイプZipList (Sum Int)
は無限ではありませんどちらか。
具体的にmempty :: ZipList a
は、これは無限のリストであるため、QuickCheckが値を任意に生成することは決してないことを意味します。
では、なぜ最初の3つはパスするのに、最後の3つはスタックオーバーフローで失敗するのでしょうか。最初の3つのテストでは、無限リストを無限リストと比較しようとすることは決してありません。理由を見てみましょう。
- 最初の2回のテストでは、我々は見ている
x <> mempty == x
とmempty <> x == x
。どちらの場合も、x
は「任意の」値の1つであり、無限になることはないため、この等式が無限ループになることはありません。 - 3番目のテストでは、我々は2つの有限ZipListsを生成している
x
とy
し、mappend
それらを一緒にINGの。これについては何も無限ではありません。 - 3番目のケースでは、ZipListのリストを生成し、リストを生成して
mconcat
います。しかし、リストが空の場合はどうなりますか?さて、、mconcat [] = mempty
そして空のリストを折りたたむと、が生成されmempty
ます。つまり、空のリストが任意の入力として生成された場合(これは完全に可能です)、テストは無限リストが別の無限リストと等しいことを確認しようとします。これにより、常にスタックオーバーフローまたはブラックホールが発生します。
どうすればこれを修正できますか?私は2つの方法を思い付くことができます:
あなたは、独自のバージョンを定義することができる
EqProp
ためZipList
、それが唯一のリストの一部有限プレフィックスに平等を比較するようにします。これにはnewtype MonZipList a = MonZipList (ZipList a)
、(おそらく)newtypeラッパーを作成し、多数のインスタンスを派生させてから、EqProp
手動でインスタンスを作成することが含まれる可能性があります。これはおそらく機能しますが、少しエレガントではありません。monoid
4番目のテストの異なるバージョンを使用する独自のバージョンを作成できます。たとえば、テストで空でないリストのみを使用するように制限すると、問題は発生しません。これを行うには、monoidプロパティテストの定義を確認することから始める必要があります。現在、「mconcat」プロパティを次のようproperty mconcatP
に定義していることに注意してください。
mconcatP :: [a] -> Property
mconcatP as = mconcat as =-= foldr mappend mempty as
QuickCheck独自のNonEmptyList
クラスを使用して、次のように目的に合わせてこれを書き直すことができます。
mconcatP :: NonEmptyList a -> Property
mconcatP (NonEmptyList as) = mconcat as =-= foldr mappend mempty as
明らかに、これは少し弱い状態ですが、少なくともハングしない状態です。