Haskell quickBatch: ZipList Monoid'i mconcat'ta test etmek yığın taşmasına neden olur
ZipList Semigroup ve Monoid için artık örnekler oluşturdum. Bununla birlikte, testleri mconcat testinde monoid üzerinde quickBatch'ten çalıştırdığımda, bir yığın taşma hatası oluşuyor. Bu hatayı nasıl çözerim? Neden böyle bir hata var? O nedeniyle mi pure mempty
ben HaskellBook Bölüm 17 Uygulamalı bölüm 17.8 ZipList Monoid bu çoğunlukla var ben oldukça anlamıyorum, hangi?
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
Yanıtlar
Evet, hata nedenidir pure mempty
, ancak bu pure mempty
yanlış olduğu anlamına gelmez . Önce oraya bakalım.
Tanımda yer alan türlere bakmak çok yardımcı olur mempty = pure mempty
:
mempty :: ZipList a
mempty = (pure :: a -> ZipList a) (mempty :: a)
Temel olarak, biz kullanacağız pure
bir yaratmak için operasyon ZipList
ait out mempty
Çeşidi a
. Buradan , purefor 'ZipList un tanımına bakmak yardımcı olur :
pure :: a -> ZipList a
pure x = ZipList (repeat x)
Toplam olarak, mempty
için ZipList a
bir olacak ZipList
sonsuz tekrar listesini ihtiva eden mempty
altta yatan Çeşidi değerleri a
.
Bu hataya geri dönüyorsunuz. Testi çalıştırmayı denediğinizde monoid
üzerinde ZipList (Sum Int)
, QuickCheck özelliklerinin bir diziyi test etmek için gidiyor.
- İlk ikisi sol kimlik ve sağ kimlik özelliklerini kontrol eder. Bunların yaptığı şey, türden değerler oluşturmak
x :: ZipList (Sum Int)
ve bunu doğrulamaktırx <> mempty = mempty <> x = x
. - Üçüncüsü, herhangi iki değer
x, y :: ZipList (Sum Int)
için bux
haritaya sahip olduğumuzu kontrol edery = x <> y
. - Dördüncüsü, herhangi bir değer listesi için
x :: [ZipList (Sum Int)]
, bunları katlamakla katlamanınmappend
aynı olup olmadığını kontrol edermconcat
.
Devam etmeden önce, "herhangi bir değer için" dediğimde, gerçekten QuickCheck'in Arbitrary
söz konusu türden değerler üretmek için söz konusu türün örneğini kullandığını belirtmek istiyorum . Bundan başka, Arbitrary
için, örneğin ZipList a
ile aynıdır Arbitrary
için, örneğin [a]
, ancak daha sonra sarılmış ZipList
. Son olarak, Arbitrary
örneği [a]
asla sonsuz bir liste oluşturmayacaktır (çünkü bunlar, sonsuz bir döngüye girme veya yığının taşması gibi eşitliği kontrol ederken sorunlara neden olacaktır), bu nedenle, türdeki "herhangi bir değer için" ZipList (Sum Int)
bunlar asla sonsuz olmayacaktır. ya.
Spesifik olarak, bu, QuickCheck'in değeri hiçbir zaman keyfi olarak üretmeyeceği anlamına gelir, mempty :: ZipList a
çünkü bu sonsuz bir listedir.
Öyleyse neden ilk 3 pas geçiyor ama sonuncusu yığın taşmasıyla başarısız oluyor? İlk üç testte, sonsuz bir listeyi sonsuz bir listeyle karşılaştırmaya asla çalışmayız. Bakalım neden olmasın.
- İlk iki testlerde, baktığımız
x <> mempty == x
vemempty <> x == x
. Her iki durumda da,x
asla sonsuz olmayacak olan "keyfi" değerlerimizden biridir, bu nedenle bu eşitlik asla sonsuz bir döngüye girmeyecektir. - Üçüncü testte, iki sonlu ZipLists üretiyorsun
x
vey
vemappend
onları bir arada ing. Bununla ilgili hiçbir şey sonsuz olmayacak. - Üçüncü durumda, ZipListlerin bir listesini oluşturuyor ve
mconcat
listeyi oluşturuyoruz. Peki liste boşsa ne olur? Eh,mconcat [] = mempty
ve boş bir listeyi katlamak üretirmempty
. Bu, boş liste rastgele girdi olarak üretilirse (ki bu tamamen mümkündür), o zaman test sonsuz bir listenin başka bir sonsuz listeye eşit olduğunu doğrulamaya çalışacak ve bu da her zaman yığın taşması veya kara delik ile sonuçlanacaktır.
Bunu nasıl düzeltebilirsin? İki yöntem bulabilirim:
Sadece listenin bazı sonlu öneklerinde eşitliği karşılaştırması
EqProp
için kendi for sürümünüzü tanımlayabilirsinizZipList
. Bu muhtemelen yeni bir tür sarmalayıcı (belkinewtype MonZipList a = MonZipList (ZipList a)
) yapmayı , bir grup örnek türetmeyi ve ardından birEqProp
tanesini elle yazmayı içerir . Bu muhtemelen işe yarayacaktır, ancak biraz uygunsuzdur.monoid
Dördüncü testin farklı bir versiyonunu kullanan kendi versiyonunuzu yazabilirsiniz . Örneğin, testin yalnızca boş olmayan listeler kullanmasını sağlayacak şekilde kısıtlarsanız herhangi bir sorun yaşamazsınız. Bunu yapmak için , monoidmülk testlerinin tanımına bakarak başlamalısınız . Şu anda "mconcat" özelliğiniproperty mconcatP
nerede olarak tanımladığına dikkat edin
mconcatP :: [a] -> Property
mconcatP as = mconcat as =-= foldr mappend mempty as
QuickCheck'in kendi NonEmptyList
sınıfını kullanarak , bunu aşağıdaki amaçlarınız için yeniden yazabilirsiniz:
mconcatP :: NonEmptyList a -> Property
mconcatP (NonEmptyList as) = mconcat as =-= foldr mappend mempty as
Açıkçası, bu biraz daha zayıf bir durum, ama en azından takılmayacak bir durum.