Haskell quickBatch: Thử nghiệm ZipList Monoid tại mconcat dẫn đến tình trạng tràn ngăn xếp
tôi đã tạo các phiên bản mồ côi cho ZipList Semigroup và Monoid. Tuy nhiên, khi tôi chạy các bài kiểm tra từ quickBatch trên monoid, ở bài kiểm tra mconcat, có lỗi tràn ngăn xếp. Làm cách nào để giải quyết lỗi này? Tại sao lại có lỗi như vậy? Đó có phải là do pure mempty
, điều mà tôi không hiểu lắm vì tôi nhận được điều này chủ yếu từ HaskellBook Chương 17 Phần ứng dụng 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

Trả lời
Đúng, lỗi là do pure mempty
, nhưng không có nghĩa pure mempty
là sai. Hãy nhìn vào đó trước.
Nó giúp ích rất nhiều cho việc xem xét các loại liên quan đến định nghĩa mempty = pure mempty
:
mempty :: ZipList a
mempty = (pure :: a -> ZipList a) (mempty :: a)
Về cơ bản, chúng ta sẽ sử dụng các pure
hoạt động để tạo ra một ZipList
ra khỏi mempty
các loại a
. Từ đây sẽ giúp bạn xem xét định nghĩa của pureforZipList :
pure :: a -> ZipList a
pure x = ZipList (repeat x)
Tổng cộng, mempty
for ZipList a
sẽ là một ZipList
chứa danh sách các mempty
giá trị lặp lại vô hạn của kiểu cơ bản a
.
Quay lại lỗi bạn đang gặp phải. Khi bạn cố gắng chạy thử nghiệm monoid
trên ZipList (Sum Int)
, QuickCheck đang xảy ra để kiểm tra một chuỗi các tài sản.
- Hai đầu tiên kiểm tra các thuộc tính nhận dạng bên trái và bên phải. Những gì chúng làm là tạo ra các giá trị của loại
x :: ZipList (Sum Int)
và xác minh điều đóx <> mempty = mempty <> x = x
. - Lần thứ ba kiểm tra xem đối với hai giá trị bất kỳ
x, y :: ZipList (Sum Int)
, chúng tôi cóx
mappend đóy = x <> y
. - Lần thứ tư kiểm tra xem có bất kỳ danh sách giá trị nào không
x :: [ZipList (Sum Int)]
, việc gấp các giá trị nàymappend
lại giống như nhậpmconcat
chúng.
Trước khi tiếp tục, điều quan trọng cần lưu ý là khi tôi nói "cho bất kỳ giá trị nào", ý tôi thực sự là QuickCheck đang sử dụng Arbitrary
phiên bản của loại đã nói để tạo ra các giá trị của loại đó. Hơn nữa, Arbitrary
instance for ZipList a
cũng giống như Arbitrary
instance for [a]
nhưng sau đó được bao bọc trong ZipList
. Cuối cùng, Arbitrary
ví dụ for [a]
sẽ không bao giờ tạo ra một danh sách vô hạn (vì chúng sẽ gây ra vấn đề khi bạn đang kiểm tra tính bình đẳng, như đi vào một vòng lặp vô hạn hoặc làm tràn ngăn xếp), vì vậy những kiểu "cho bất kỳ giá trị nào" ZipList (Sum Int)
này sẽ không bao giờ là vô hạn hoặc.
Cụ thể, điều này có nghĩa là QuickCheck sẽ không bao giờ tự ý tạo giá trị mempty :: ZipList a
vì đây là danh sách vô hạn.
Vậy tại sao 3 cái đầu tiên vượt qua nhưng cái cuối cùng lại bị lỗi tràn ngăn xếp? Trong ba bài kiểm tra đầu tiên, chúng tôi không bao giờ kết thúc việc cố gắng so sánh một danh sách vô hạn với một danh sách vô hạn. Hãy xem tại sao không.
- Trong hai thử nghiệm đầu tiên, chúng tôi đang xem xét
x <> mempty == x
vàmempty <> x == x
. Trong cả hai trường hợp,x
là một trong những giá trị "tùy ý" của chúng ta, sẽ không bao giờ là vô hạn, vì vậy đẳng thức này sẽ không bao giờ đi vào một vòng lặp vô hạn. - Trong thử nghiệm thứ ba, chúng tôi đang tạo ra hai ZipLists hữu hạn
x
vày
vàmappend
ing chúng lại với nhau. Không có gì về điều này sẽ là vô hạn. - Trong trường hợp thứ ba, chúng tôi đang tạo một danh sách ZipLists và
mconcat
tạo ra danh sách. Nhưng, điều gì sẽ xảy ra nếu danh sách trống? Vâng,mconcat [] = mempty
và gấp một danh sách trống sẽ tạo ramempty
. Điều này có nghĩa là, nếu danh sách trống được tạo dưới dạng đầu vào tùy ý (điều này hoàn toàn có thể xảy ra), thì kiểm tra sẽ cố gắng xác nhận rằng một danh sách vô hạn bằng với một danh sách vô hạn khác, điều này sẽ luôn dẫn đến tràn ngăn xếp hoặc lỗ đen.
Làm thế nào bạn có thể sửa chữa điều này? Tôi có thể đưa ra hai phương pháp:
Bạn có thể xác định phiên bản của riêng mình
EqProp
choZipList
để nó chỉ so sánh bình đẳng trên một số tiền tố hữu hạn của danh sách. Điều này có thể liên quan đến việc tạo một trình bao bọc kiểu mới (có lẽnewtype MonZipList a = MonZipList (ZipList a)
), tạo ra một loạt các trường hợp, và sau đó viết một bản saoEqProp
bằng tay. Điều này có thể sẽ hiệu quả nhưng hơi không phù hợp.Bạn có thể viết phiên bản của riêng mình bằng cách
monoid
sử dụng một phiên bản khác của bài kiểm tra thứ tư. Ví dụ: nếu bạn hạn chế nó để thử nghiệm chỉ sử dụng các danh sách không trống, thì bạn sẽ không gặp vấn đề gì. Để làm điều này, bạn nên bắt đầu bằng cách xem định nghĩa của các monoidbài kiểm tra thuộc tính . Lưu ý rằng nó hiện định nghĩa thuộc tính "mconcat" làproperty mconcatP
nơi
mconcatP :: [a] -> Property
mconcatP as = mconcat as =-= foldr mappend mempty as
Sử dụng NonEmptyList
lớp riêng của QuickCheck , bạn có thể viết lại lớp này cho các mục đích của mình như:
mconcatP :: NonEmptyList a -> Property
mconcatP (NonEmptyList as) = mconcat as =-= foldr mappend mempty as
Rõ ràng, đây là một tình trạng yếu hơn một chút, nhưng ít nhất nó là một trong những điều kiện sẽ không bị treo.