1

I'm playing with QuickCheck, and stumbled upon some strange behavior

sample $ elements [1..5]

works as expected, however

sample $ elements [1..]

hangs in ghci, even when using a finite type such as Int

sample $ elements [(1::Int)..]

Why doesn't it print arbitrary (pun intended :) large Ints?

Update

I've tested @amalloy's explanantion by using

sample $ elements ([1 .. ] :: [Int8])

which does terminate.

dimid
  • 7,285
  • 1
  • 46
  • 85

2 Answers2

4

elements chooses elements uniformly at random, which means on average it will be reaching length / 2 items into the list. For infinite values this is impossible, and for large finite lists like [1..] :: [Int], this is still reaching like 1 billion items in, one at a time through the linked list. Quite a slow operation!

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • Thanks, why is it "reaching length / 2 items" on average? – dimid Sep 08 '16 at 02:06
  • I mean, think about it. In a list with `N` elements, what is the average index? The middle is the average, and it's at index `N/2`. Since you're choosing uniformly at random, your average index will be the average index of the list. – amalloy Sep 08 '16 at 02:08
  • Right, but why would you need to traverse the first `[len/2] - 1` items? If you know the maximum `Int`, you can get it in O(1) by dividing in 2. Sorry if I'm missing something obvious, I'm a begginer with haskell. – dimid Sep 08 '16 at 02:16
  • 1
    Lists in Haskell are linked lists, always. – amalloy Sep 08 '16 at 02:16
  • 1
    I see, so even integer lists don't have random access? This sounds awfully inefficient. – dimid Sep 08 '16 at 02:18
  • 2
    You use something other than a list when you want random access. An array, for example. – amalloy Sep 08 '16 at 02:36
  • 1
    @dimid It is indeed inefficient, when you need random access. In many cases, though, you don't. Also, consider simple code like `let list = ... in (1:list, 2:list)`. Linked lists can build the last pair in O(1), when arrays can not. And arrays can never express infinite (lazy) lists like `[1..] :: [Integer]`, which are sometimes convenient to use. – chi Sep 08 '16 at 07:45
  • @chi _contiguous_ arrays can't do this, but [chunked arrays](https://en.wikipedia.org/wiki/Hashed_array_tree) can. – Will Ness Mar 04 '22 at 04:13
  • @amalloy people always say [this](https://stackoverflow.com/questions/39381379/create-an-elements-generator-with-an-infinite-list#comment66090836_39381473), but nowhere in the Report does it say this (that I could find). "linked" lists specifically and crucially allow for an O(1) removal of any internal node, given a pointer to a previous one. – Will Ness Mar 04 '22 at 04:19
2

It seems that elements is poorly documented. In addition to being non-empty, the argument should be finite. See the source code:

-- | Generates one of the given values. The input list must be non-empty.
elements :: [a] -> Gen a
elements [] = error "QuickCheck.elements used with empty list"
elements xs = (xs !!) `fmap` choose (0, length xs - 1)

It tries to compute the length of the input list, which will cause an infinite loop on an infinite list.

crockeea
  • 21,651
  • 10
  • 48
  • 101
  • 1
    Actually, as amalloy pointed out, the list in question is finite due to the type `[Int]`. Depending on your machine, `Int` may be 32 or 64 bits. In the former case, a computer with a large amount of RAM may terminate (eventually). In the latter case, you will definitely run out of memory. – crockeea Sep 08 '16 at 02:03