If we look at the API reference for the Arb
module, and hover over the definition of filter
, you'll see that the type of Arb.filter
is:
pred:('a -> bool) -> a:Arbitrary<'a> -> a:Arbitrary<'a>
This means that the predicate should be a function of one parameter that returns a bool
. But your length
function is a function of two parameters. You want to turn it into a function of just one parameter.
Think of it this way. When you write Arb.filter length Arb.from<int list>
, what you're saying is "I want to generate an arbitrary int list
(just one at a time), and filter it according to the length
rule." But the length
rule you've written takes two lists and compares their length. If FsCheck generates just a single list of ints, what will it compare its length to? There's no second list to compare to, so the compiler can't actually turn your code into something that makes sense.
What you probably wanted to do (though there's a problem with this, which I'll get to in a minute) was generate a pair of lists, then pass it to your length
predicate. I.e., you probably wanted Arb.from<int list * int list>
. That will generate a pair of integer lists, completely independent from each other. Then you'll still get a type mismatch in your length
function, but you just have to turn its signature from let length xs ys =
to let length (xs,ys) =
, e.g. have it receive a single argument that contains a pair of lists, instead of having each list as a separate argument. After those tweaks, your code looks like:
let length (xs,ys) =
List.length xs = List.length ys
let samelength =
Arb.filter length Arb.from<int list * int list>
But there are still problems with this. Specifically, if we look at the FsCheck documentation, we find this warning:
When using Gen.filter
, be sure to provide a predicate with a high chance of returning true
. If the predicate discards 'too many' candidates, it may cause tests to run slower, or to not terminate at all.
This applies to Arb.filter
just as much as to Gen.filter
, by the way. The way your code currently stands, this is a problem, because your filter will discard most pairs of lists. Since the lists are generated independently of each other, it will most often happen that they have different lengths, so your filter will return false
most of the time. I'd suggest a different approach. Since you've said that this is an exercise, I won't write the code for you since you'll learn more by doing it yourself; I'll just give you an outline of the steps you'll want to take.
- Generate a non-negative int
n
that will be the size of both lists in the pair. (For bonus points, use Gen.sized
to get the "current size" of the data you should generate, and generate n
as a value between 0 and size
, so that your list-pair generator, like FsCheck's default list generator, will create lists that start small and slowly grow larger).
- Use
Gen.listOfLength n
to generate both lists. (You could even do Gen.two (Gen.listOfLength n)
to easily generate a pair of lists of the same size).
- Don't forget to write an appropriate shrinker for a pair of lists, because the exercise wants you to generate a proper
Arbitrary
, and an Arbitrary
that doesn't have a shrinker is not very useful in practice. You can probably do something with Arb.mapFilter
here, where the mapper is id
because you're already generating lists of matching length, but the filter is your length
predicate. Then use Arb.fromGenShrink
to turn your generator and shrinker functions into a proper Arbitrary
instance.
If that outline isn't enough for you to get it working, ask another question about wherever you're stuck and I'll be glad to help out however I can.
Edit:
In your edit where you're trying to write a list generator using sizegen
, you have the following code that doesn't work:
let listgen =
let size = sizegen
let xs = Gen.listOfLength size
let ys = Gen.listOfLength size
xs, ys
Here sizegen
is a Gen<int>
and you're wanting to extract the int
parameter from it. There are several ways to do this, but the simplest is the gen { ... }
computation expression that FsCheck has provided for us.
BTW, if you don't know what computation expressions are, they're some of F#'s most powerful features: they are highly complex under the hood, but they allow you to write very simple-looking code. You should bookmark https://fsharpforfunandprofit.com/series/computation-expressions.html and https://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my.html and plan to read them later. Don't worry if you don't understand them on your first, or second, or even fifth reading: that's fine. Just keep coming back to these two series of articles, and using computation expressions like gen
or seq
in practice, and eventually the concepts will become clear. And every time you read these series, you'll learn more, and get closer to that moment of enlightenment when it all "clicks" in your brain.
But back to your code. As I said, you want to use the gen { ... }
computation expression. Inside a gen { ... }
expression, the let!
assignment will "unwrap" a Gen<Foo>
object into the generated Foo
, which you can then use in further code. Which is what you want to do with your size
int. So we'll just wrap a gen { ... }
expression around your code, and get the following:
let listgen =
gen {
let! size = sizegen
let xs = Gen.listOfLength size
let ys = Gen.listOfLength size
return (xs, ys)
}
Note that I also added a return
keyword on the last line. Inside a computation expression, return
has the opposite effect of let!
. The let!
keyword unwraps a value (the type goes from Gen<Foo>
to Foo
), while the return
keyword wraps a value (the type goes from Foo
to Gen<Foo>
). So that return
line takes an int list * int list
and turns it into a Gen<int list * int list>
. There's some very complex code going on under the hood, but at the surface level of the computation expression, you just need to think in terms of "unwrapping" and "wrapping" types to decide whether to use let!
or return
.