[erlang-questions] Quickcheck/PropEr: List generator not working as expected

Kostis Sagonas kostis@REDACTED
Wed Oct 19 23:17:39 CEST 2011


On 10/19/11 15:29, Ward Bekker wrote:
> See git@REDACTED:wardbekker/proper_test.git , src/test.erl for a runnable demo app. Start with ./shell from the projects root
>
> When I test the property `proper:quickcheck(test:prop_ends_with_double_cons_is_true()).` The following error is outputted to console:
>
> Error: Couldn't produce an instance that satisfies all strict constraints after 50 tries.
>
> Is seems that `?SUCHTHAT(Drow, list(consonant()), length(Drow)>  2)` can't generate valid values.
>
> When I output the values generated by `list(consonant())` I notice that only zero or one length lists are generated. Why is that?

First of all, regarding PropEr, you may be getting better/faster answers 
if you use the e-mail address in the cc: of this mail instead of the 
erlang-questions. This is because only few of the PropEr developers read 
this list.

Now, concerning your question, you already got some answers that helped 
you fix the problem you are facing, but I am not sure they are the 
proper (TM) answers, so let me also give it a try.

I see that in your program you have the following:

-define(VOWELS, [$a, $e, $i, $o, $u, $y]).

So far so good. Then you define the following:

-spec consonant() -> proper:test().
consonant() ->
     ?SUCHTHAT(Char, char(), lists:all(fun(C) -> Char /= C end, ?VOWELS)).

Let mention that the above:
  1. does not define a proper test; instead, it defines a generator
  2. seems a complicated (and a bit weird) way of writing a definition 
for consonants...
However, these remarks are besides the point here.

What you should keep in mind about generators is that they are *lazy*. 
This is very important here. What this means is that they do not get 
evaluated to some concrete value (say $c) but instead they produce a 
closure which knows how to generate (new) values when needed. So, your 
use in:

    ?SUCHTHAT(Drow, list(consonant()), length(Drow) >  2)

basically does the following:

  1. It sees the consonant() and list(consonant()) generators and 
generates symbolic terms for them.

  2. To be able to see whether the term generated for list(consonant()) 
satisfies the length(Drow) > 2 condition, the generator gets evaluated. 
Let's assume that the term that the consonant() generator gets evaluated 
is $c. Then a list($c) will be generated and since lists start small, 
these are quite often the empty list [] or the list [$c] with just one 
element.

  3. Since for the above the ?SUCHTHAT condition is not satisfied, 
PropEr will try to generate a new term for the list(consonant()) generator.

  4. Now, you may think that hey, the consonant() generator has already 
been evaluated to a value, namely $c, so a new list of $c's will be 
generated next, and since the empty list and the one element list have 
already been generated, a bigger list of $c's will be generated next. 
However, that's NOT what's happening here!

  5. Instead, the consonant() generator is re-evaluated so a new term 
gets produced, let's say $d, and a new list($d) generator will be 
evaluated next.

Now, I hope that you can see where this is heading...  list($d) is a 
totally new generator and has similarly equal probability of generating 
a small list (empty or just one element) as the previous generator 
(list($c)) had.

Anyway, the point is that the consonant() generator may look like an 
Erlang function, but it's not evaluated to a value once and for all. 
Instead, it's evaluated lazily.  You should keep this in mind because 
it's a subtle point in an otherwise eager language like Erlang.

Hope this helps,
Kostis



More information about the erlang-questions mailing list