Here are the type signatures of chunksOf
and splitPlaces
from Data.List.Split
:
chunksOf :: Int -> [e] -> [[e]]
splitPlaces :: Integral a => [a] -> [e] -> [[e]]
Why do some functions (like chunksOf
) use Int
while others (like splitPlaces
) use the more generic Integral a
?
In this answer, I try to look at the historical reasons for the inconsistent interface. Summary: It seems that Brent made some functions more generic on the fly to help formulating QuickCheck properties.
splitPlaces
generic?It looks as if Brent generalized the type of splitPlaces
to make it easier to introduce QuickCheck properties for that function. The QuickCheck properties use newtype wrappers to control test case generation, and with the Integral a
constraint, splitPlaces
could look through this newtype wrapper for the arithmetic operations. See also:
The patch
that generalizes the type of splitPlaces
and introduces the QuickCheck properties.
The QuickCheck documentation about these newtype wrappers.
However, here is one of the properties about splitPlaces
:
prop_splitPlaces_preserve :: [NonNegative Integer] -> [Elt] -> Bool
prop_splitPlaces_preserve ps l = concat (splitPlaces ps' l) == genericTake (sum ps') l
where ps' = map unNN ps
Note that QuickCheck automatically generates the list ps
which is converted by map unNN ps
before being passed to splitPlaces
. The unNN
function removes the NonNegative
wrapper, so splitPlaces
does not have to deal with the NonNegative
wrapper itself. However, it receives an argument of type [Integer]
instead of [Int]
, so it still needs to be generic in the numeric type.
[NonNegative Integer]
instead of [NonNegative Int]
?I suspect that the property is false for [Int]
because of arithmetic overflows when computing the sum. The property might even be falsifiable by QuickCheck because the Arbitrary [NonNegative Integer]
instance will ultimately delegate to arbitrarySizedBoundedIntegral
which can generate very large values.
I guess that using [NonNegative Integer]
instead circumvents this problem in two ways:
Integer
, no overflows can occur.Arbitrary Integer
instance delegates to arbitrarySizedIntegral
which only generates small values.So I guess that the reason for allowing arbitrary Integral types is that the QuickCheck property would fail for Int
but succeeds for Integer
.
chunksOf
not generic?The properties for chunksOf
use pattern matching to remove the newtype wrappers. See also:
The patch that introduces properties for splitEvery
.
The patch that renames splitEvery
to chunksOf
.
Here is one of the properties about chunksOf
:
prop_chunksOf_all_n :: Positive Int -> NonEmptyList Elt -> Bool
prop_chunksOf_all_n (Positive n) (NonEmpty l) = all ((==n) . length) (init $ chunksOf n l)
Note that this property matches on the arguments that are automatically generated by QuickCheck and passes them to chunksOf
without the newtype wrapper. For the arguments necessary for testing chunksOf
, this is easy to do, because the numbers are not nested in other types. Compare with prop_splitPlaces_preserve
above, where converting [NonNegative Integer]
to [Integer]
or [Int]
requires something more complicated than just pattern matching.
The difference between Arbitrary Int
and Arbitrary Integer
doesn't matter here, because the property does not involve any operations that can trigger an arithmetic overflow.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With