bình luận DarkOtter của đề cập QuickCheck của Arbitrary
và CoArbitrary
lớp học, mà chắc chắn là điều đầu tiên bạn nên thử. QuickCheck có trường hợp này:
instance (CoArbitrary a, Arbitrary b) => Arbitrary (a -> b) where ...
Vì điều này xảy ra, hôm qua tôi đã đọc mã QuickCheck để hiểu cách thức hoạt động, vì vậy tôi có thể chia sẻ những gì tôi đã học được trong tâm trí. QuickCheck được xây dựng xung quanh một loại trông như thế này (và điều này sẽ không hoàn toàn giống nhau):
type Size = Int
-- | A generator for random values of type @[email protected]
newtype Gen a =
MkGen { -- | Generate a random @[email protected] using the given randomness source and
-- size.
unGen :: StdGen -> Size -> a
}
class Arbitrary a where
arbitrary :: a -> Gen a
Bí quyết đầu tiên là QuickCheck có một chức năng mà làm việc như thế này (và tôi đã không làm việc ra chính xác cách nó thực hiện):
-- | Use the given 'Int' to \"perturb\" the generator, i.e., to make a new
-- generator that produces different pseudorandom results than the original.
variant :: Int -> Gen a -> Gen a
Sau đó, họ sử dụng để thực hiện các trường hợp khác nhau của CoArbitrary
lớp này:
class CoArbitrary a where
-- | Use the given `a` to perturb some generator.
coarbitrary :: a -> Gen b -> Gen b
-- Example instance: we just treat each 'Bool' value as an 'Int' to perturb with.
instance CoArbitrary Bool where
coarbitrary False = variant 0
coarbitrary True = variant 1
Bây giờ với những mảnh tại chỗ, chúng tôi muốn điều này:
instance (Coarbitrary a, Arbitrary b) => Arbitrary (a -> b) where
arbitrary = ...
Tôi sẽ không viết ra thực hiện, nhưng ý tưởng là thế này:
- Sử dụng
CoArbitrary
thể hiện của a
và Arbitrary
thể hiện của b
chúng ta có thể thực hiện chức năng \a -> coarbitrary a arbitrary
, trong đó có gõ a -> Gen b
.
- Hãy nhớ rằng
Gen b
là kiểu mới cho StdGen -> Size -> b
, do đó, loại a -> Gen b
là đẳng cấu để a -> StdGen -> Size -> b
.
- Chúng ta có thể viết một hàm nhỏ bất kỳ chức năng nào của loại thứ hai đó và chuyển thứ tự đối số xung quanh để trả về một hàm thuộc loại
StdGen -> Size -> a -> b
.
- Loại sắp xếp lại này là đẳng cấu để
Gen (a -> b)
, do đó, chúng tôi đóng gói chức năng sắp xếp lại thành một Gen
và chúng tôi có bộ tạo hàm ngẫu nhiên!
Tôi khuyên bạn nên đọc nguồn QuickCheck để xem điều này cho chính mình. Khi bạn giải quyết điều đó, bạn sẽ chỉ chạy vào hai chi tiết bổ sung có thể làm chậm bạn xuống. Thứ nhất, lớp Haskell RandomGen
có phương pháp này:
-- | The split operation allows one to obtain two distinct random generators.
split :: RandomGen g => g -> (g, g)
Thao tác này được sử dụng trong Monad
dụ cho Gen
, và là khá quan trọng. Một trong những thủ thuật ở đây là StdGen
là một trình tạo số ngẫu nhiên giả ngẫu nhiên; cách hoạt động Gen (a -> b)
là cho mỗi giá trị có thể của a
chúng tôi làm nhiễu máy phát điện b
, hãy sử dụng máy phát điện bị nhiễu loạn đó để tạo kết quả b
, nhưng sau đó chúng tôi không bao giờ chuyển trạng thái của máy phát điện bị nhiễu loạn; về cơ bản, hàm a -> b
được tạo ra là một đóng trên một hạt giả ngẫu nhiên, và mỗi lần chúng ta gọi nó là a
để sử dụng cụ thể đó là a
để xác định một hạt giống mới, và sau đó sử dụng nó để xác định một b
phụ thuộc vào a
và hạt giống ẩn.
Loại viết tắt Seed -> a -> b
nhiều hay ít tổng kết những gì đang xảy ra — chức năng giả ngẫu nhiên là quy tắc tạo số b
từ hạt giả ngẫu nhiên và a
. Điều này sẽ không hoạt động với các trình tạo số ngẫu nhiên trạng thái bắt buộc.
Thứ hai: thay vì trực tiếp có chức năng (a -> StdGen -> Size -> b) -> StdGen -> Size -> a -> b
như tôi đã mô tả ở trên, mã QuickCheck có promote :: Monad m => m (Gen a) -> Gen (m a)
, là sự tổng quát hóa điều đó với bất kỳ Monad
nào. Khi m
là phiên bản chức năng của Monad
, promote
trùng với (a -> Gen b) -> Gen (a -> b)
, vì vậy nó thực sự giống như tôi phác thảo ở trên.
Khi được đánh giá đầy đủ, các chức năng đều có cùng loại kết quả không? – mhwombat
@mhwombat Có, họ sẽ. – Eyal
Các loại đối số và kết quả nào nên có?Nó có thể là giá trị xem xét ví dụ các lớp tùy ý và Cobitbitrary trong QuickCheck, được sử dụng để tạo ngẫu nhiên các chức năng cho mục đích thử nghiệm. Ngoài ra, nếu bạn thực sự cần phải bỏ qua trình kiểm tra lỗi, bạn có thể làm điều đó bằng cách sử dụng unsafeCoerce. Điều này thực sự được sử dụng trong nội bộ chức năng cast của Typeable. – DarkOtter