1

For a given predicate pred : 'a list -> bool and generator gen : Gen<'a>, consider the following generator of well-formed lists that satisfy the predicate:

let wellFormedList pred gen =
   Gen.ofList gen
   |> Gen.filter pred 

As mentioned in the FsCheck manual, there should be a high chance that predicate holds for a random list. Unfortunately, this assumption does not hold in my case. Thus, I need te define a custom generator for lists that satisfy the predicate.

How can I define a custom generator that starts from the empty list and extends it with new random elements, until the list satisfies the predicate?

I probably need to use the computation expression gen { } for generators, but I do not see how.

PS: I am aware that, unlike the original implementation of wellFormedList, the distribution of such a custom generator is not uniform over all lists that satisfy the predicate.

  • Can we assume from your question that a list that doesn't satisfy the predicate just needs more random elements added until it does? There's no need to throw out a list that doesn't satisfy the predicate, we should just append more elements to it? – Nathan Wilson Mar 22 '19 at 16:35
  • Yes, that is correct. – Kasper Dokter Mar 22 '19 at 21:31

1 Answers1

1

Well if I'm understanding it right, I think this should do it:

let wellFormedList (predicate:'a list -> bool) (myGen:Gen<'a>) = 
  gen {
    let! initialList = Gen.listOf myGen
    let mutable myList = initialList
    while not <| predicate myList do
      let! newVal = myGen
      myList <- (newVal :: myList)

    return myList      
  }

or if you wanted a recursive function:

let wellFormedList (predicate:'a list -> bool) (myGen:Gen<'a>) = 
  gen {
    let rec addIfNecessary listSoFar =
      if predicate listSoFar 
      then gen { return listSoFar }
      else gen { 
        let! newVal = myGen
        return! addIfNecessary (newVal :: listSoFar)
      }

    let! initialList = Gen.listOf myGen
    return! addIfNecessary initialList
  }

I haven't checked what kind of distributions that gives you though. And of course you risk an infinite loop (or stack overflow) if the list never converges on a form that passes the predicate.

Nathan Wilson
  • 629
  • 6
  • 11
  • Thanks, this is what I was looking for. There is a bug in the code: please change `initialList` to `myList` in the while loop and the return statement. – Kasper Dokter Mar 22 '19 at 21:49
  • Could you explain the purpose of the exclamation mark in the `let!`? – Kasper Dokter Mar 22 '19 at 22:04
  • 1
    Sure! So anywhere within a computation expression (the `gen { }` block here), if you say `let! x = a`, if `a` is a `Gen`, the `let!` "unwraps" it, so `x` would just be `int`. In general the thing on the right hand side has to be a "wrapped" thing if there's an exclamation point. (That's why I use `return!` when I'm returning something that's already a `Gen<_>`, but just plain `return` when returning something that isn't a `Gen<_>`). – Nathan Wilson Mar 22 '19 at 22:07
  • So in `let! initialList = Gen.listOf myGen`, to the right of the `=` is a `Gen<'a list>`, and `initialList` is just plain `'a list`. In `let! newVal = myGen`, `myGen` is a `Gen<'a>`, but `newVal` is just plain `'a` – Nathan Wilson Mar 22 '19 at 22:09