3

I need some help to do this exercise about generators in f#.

The functions

List.zip : ('a list -> 'b list -> ('a * 'b) list)

and

List.unzip : (('a * 'b) list -> 'a list * 'b list)

are inverse of each other, under the condition that they operate on lists of the same length. Formulate an Arbitrary for pair of lists of ints of the same length

I tried to write some code:

let length xs ys = 
    List.length xs = List.length ys

let samelength = 
    Arb.filter length Arb.from<int list>

It doesn't work, I get a type mismatch at length in samelength:

Error: type mismatch. Expecting a 'a list -> bool but given a 'a list -> 'b list -> bool. The type bool does not match the type 'a list -> bool.

Edit:

As suggested I tried to follow the outline of steps but I'm stuck.

let sizegen =
    Arb.filter (fun x -> x > 0) Arb.from<int>

let listgen =
    let size = sizegen 
    let xs = Gen.listOfLength size
    let ys = Gen.listOfLength size
    xs, ys

And of course I have the error type mismatch:

Error: type mistmatch. Expected to have type int but here has type Arbitrary<int>

Edit

I solved the exercise but it seems that my generator is not working when I do the test, it looks another one is invoked.

let samelength (xs, ys) = 
   List.length xs = List.length ys

let arbMyGen2 = Arb.filter samelength Arb.from<int list * int list> 

type MyGeneratorZ =
   static member arbMyGen2() = 
    {
        new Arbitrary<int list * int list>() with
            override x.Generator = arbMyGen2 |> Arb.toGen
            override x.Shrinker t = Seq.empty
    }

let _ = Arb.register<MyGeneratorZ>()

let pro_zip (xs: int list, ys: int list) = 
   (xs, ys) = List.unzip(List.zip xs ys)

do Check.Quick pro_zip

I get the error:

Error:  System.ArgumentException: list1 is 1 element shorter than list2

But why? My generator should only generate two lists of the same length.

bennius
  • 111
  • 7
  • What is the exact error? – glennsl May 28 '19 at 15:00
  • Error: type mismatch. Expecting a 'a list -> bool but given a 'a list -> 'b list -> bool. The type bool does not match the type 'a list ->bool – bennius May 28 '19 at 15:02
  • @bennius - I fixed the angle brackets in your code; you probably were having trouble with them because the code wasn't yet indented when you typed them, so Stack Overflow was treating them as incorrect HTML and stripping them. If you have a code block that's indented, then you can include angle brackets inside it just fine. Please double-check my edit and make sure I got it right. :-) – rmunn May 28 '19 at 15:05
  • @rmunn thank you! It's correct now :) – bennius May 28 '19 at 15:06

1 Answers1

4

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.

  1. 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).
  2. 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).
  3. 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.

rmunn
  • 34,942
  • 10
  • 74
  • 105
  • thank you for your help! I'll try to do the exercise following your outline. I'll let you know! :) – bennius May 28 '19 at 15:48
  • @bennius - See my edit, and don't forget to bookmark the two series of articles that I mentioned. You'll really gain a LOT of knowledge from them over time, even though I guarantee you won't understand them at first. Just keep coming back to them every so often as your experience with F# grows. – rmunn May 28 '19 at 16:58
  • I solved it but I get an error during the test. It looks my generator is not working properly. – bennius May 29 '19 at 15:06
  • @bennius - I suggest you ask a new question about the error, since this one is getting a bit over-edited and the question subject no longer reflects the problem. I think your issue is that you're using `Check.Quick`, which ignores the generator you've registered, and that you actually need to be using something like `Check.One` which takes a `config` parameter. But asking a new question will ensure that more people than me see it, and someone else might have a better idea of the problem. – rmunn May 29 '19 at 15:22