1

So I am writing a program to generate a list of prime numbers in haskell. I create two functions shown below:

{-
Given a list of prime numbers, this function will
add the next prime number to the list. So, given the 
list [2, 3], it will return [2, 3, 5]
-}
nextPrime xs =  xs ++ [lastVal + nextCounts]
    where
        lastVal       =  (head . reverse) $ xs
        isNextPrime y =  0 `elem` ( map ( y `mod`) xs )
        nextVals      =  (map isNextPrime [lastVal, lastVal+1 ..] )
        nextCounts    =  length $ takeWhile (\x -> x) nextVals


allPrimes xs = allPrimes np
    where 
        np = nextPrime xs

Now the function 'nextPrime' is doing what it is supposed to do. However, when I do a call to allPrimes as shown below:

take 5 $ allPrimes [2,3]

The program goes into an infinite loop. I thought Haskells "lazy" features were supposed to take care of all this? What am I missing??

2
  • 1
    Question: When does allPrimes produces the first value? Ans: Never. It just calls it self with a larger list and this step never ends. A trivial solution is to produce some result before recursing again. Commented Sep 29, 2013 at 16:24
  • I would suggest instead of writing a function that takes a list and then returns a new list with the next prime, why not just make a function that produces an infinite list of primes? Haskell uses lazy evaluation, so you only compute an element of the list when you ask for it. Commented Sep 29, 2013 at 16:26

3 Answers 3

3

If you start evaluating the expression on paper you can see why laziness doesn't help here. Start with your expression:

take 5 $ allPrimes [2,3]

First, attempt to evaluate the allPrimes expression:

allPrimes [2, 3]

which becomes

allPrimes np
    where 
        np = nextPrime [2, 3]

put the things from the where clause into the expression and it becomes

allPrimes (nextPrime [2, 3])

Now, evaluate nextPrime [2, 3] (you can do this in ghci since that function works) and get [2, 3, 5], which you can replace in the previous expression, and it becomes

allPrimes [2, 3, 5]

repeat the above and it becomes

allPrimes [2, 3, 5, 7]

and there is your problem! allPrimes never evaluated to any values, it evaluates to allPrimes applied to longer and longer lists. To see where laziness does work, try evaluating on paper a function like zip from the Prelude:

zip :: [a] -> [b] -> [(a,b)]
zip (a:as) (b:bs) = (a,b) : zip as bs

zip [1, 2, 3] ['a', 'b', 'c']

a becomes 1, as becomes [2, 3], b becomes 'a', bs becomes ['b', 'c'] so you get

(1, 'a') : zip [2, 3] ['b', 'c']

The difference here is that there is a list with a value, then the rest of the list is an expression. In your allPrimes function, you just keep getting more expressions.

For more information, look into Weak Head Normal Form however if you are new to Haskell I recommend you get comfortable with the syntax and with the basics of "Thinking in Haskell" before you start looking at things like WHNF.

Sign up to request clarification or add additional context in comments.

4 Comments

That [(1, 'a'), zip ... thing is actually ill-typed, since zip returns a list of tuples.
True. I'll Remove it.
@Drew: nothing forces the call to nextPrime [2, 3], so wouldn't it be allPrimes (nextPrime (nextPrime ... [2, 3])...)?
I don't think so, but I don't know enough about graph reduction to say for sure.
1

I'd read Drew's answer for a good explanation of what's going wrong, but for a quick demonstration for how to make this work,

nextPrime xs =  xs ++ [lastVal + nextCounts]
  where
    lastVal       =  (head . reverse) $ xs
    isNextPrime y =  0 `elem` ( map ( y `mod`) xs )
    -- ^ Style note, this name is super misleading, since it returns
    -- false when the number is prime :)
    nextVals      =  (map isNextPrime [lastVal, lastVal+1 ..] )
    nextCounts    =  length $ takeWhile (\x -> x) nextVals


allPrimes xs = last np : allPrimes np
  where np = nextPrime xs

Now we're constructing the list as we go, and haskell is lazy so it can grab the last element of np before evaluating the allPrimes np. In other words head (a : infiniteLoop) is a, not an infinite loop.

However this is really innefficient. Lists are singly linked in Haskell so last is O(n) as opposed to O(1) in something like Python. And ++ is also costly, O(n) for the length of the first list.

Instead

 nextPrime xs = lastVal + nextCounts
   where lastVal     = head xs
         isNextPrime = 0 `elem` map (y `rem`) xs
         nextVals    = map isNextPrime [lastVal ..]
         nextCount   = length $ takeWhile id nextVals

 allPrimes xs = p : allPrimes (p:xs)
    where p = nextPrime xs

So we keep the list reversed to avoid those costly traversals. We can also simplify nextPrime

import Data.Maybe
nextPrime xs = fromJust nextPrime
  where isPrime y =  not $ 0 `elem` map (rem y) xs
        nextPrime = find isPrime [head xs ..]

Where we just search the list for the first element which is prime and add it to our list. The fromJust is normally bad, if there were no next primes we'd get an error. But since we know mathematically that there will always be a next prime, this is safe.

In the end, the code looks like

 import Data.Maybe
 import Data.List
 nextPrime xs = fromJust nextPrime
   where isPrime y = 0 `notElem` map (rem y) xs
         nextPrime = find isPrime [head xs ..]
 allPrimes xs = p : allPrimes (p:xs)
   where p = nextPrime xs

To evaluate it, call allPrimes [2].


An even cleaner way to do this would be to have a function isPrime that returns whether a number is prime or not. And then just to have

allPrimes = filter isPrime [1..]

But I'll leave that to the curious reader.

4 Comments

While your suggestion is cleaner, it would probably be slower. An isPrime a function can be implemented more efficiently if it has a list of all the primes before a.
@Drew This is true, that's why is said cleaner not faster :)
Fair enough. Personally I hesitate to recommend a cleaner but slower solution, that seems like the wrong tradeoff to me.
@Drew I mentioned it because filter is a very common idiom and usually worth bringing up in any post on list processing. But the more efficient implementation is 10 lines above it so I think it's ok here
0

As Drew pointed out, your function allPrimes doesn't profit from lazyness since we never have acess to what it calculates. This is because the list we want to peek into is an argument of allPrimes, not a return value.

So we need to expose the list allPrimes is building, and still keep a function call that will infinitely build the following value of this list.

Well, since allPrimes is the re-application of itself over and over, we just need a function that exposes the intermediate values. And we have one!

 iterate f a == [a, f (f a),...]

So with iterate and nextPrime, we could build the following (rather strange) functions:

-- nextPrime renamed as nextPrimeList
infiniteListofListofPrimes =  iterate nextPrimeList [2,3]
primeN n =   (infiniteListofListofPrimes !! n) !! n
takeN n  =  take n (infiniteListofListofPrimes !! n)

We are generating our primes, but it's not looking great. We would rather have [primes], not redundant [[some primes]].

The next step is building the list on WHNF:

elem1:elem2:aux
where aux = newvalue:aux

Where aux will calculate the newvalue and leave everything in place for the next one.

For that we need nextPrime sticking to generating one new prime:

nextPrime xs = lastVal + nextCounts

And finding the aux that can build listOfPrimes forever.

I came up with this:

  infiniteListofPrimes = 2:3:aux 2
       where aux n  = nextPrime (take n infiniteListofPrimes):(aux (n+1))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.