ваш код,
primes n = [i | i <- [1,3..n], mod i k /= 0
| k <- primes (isqrt i)]
интерпретируется как параллельное понимание списка;т.е. вместо объединения двух генераторов обычным вложенным способом, они объединяются параллельно:
primes n = [i | (i,k) <- zip [i | i <- [1,3..n], mod i k /= 0]
-- not in scope: k ^
[k | k <- primes (isqrt i)] ]
-- not in scope: i ^
Вместо этого, чтобы выразить то, что вы намеревались, это можно записать как
primes 1 = []
primes n = 2:[i | i <- [3,5..n], and [mod i k /= 0 | k <- primes (isqrt i)]]
, таким образом, имея понимание списка внутри понимания списка - но как часть охраны, а не генератора.Функция and :: [Bool] -> Bool
делает это возможным.
Кстати, рекурсивный вызов primes
для каждого нового кандидата i
делает его медленным, а время выполнения слишком быстрым, эмпирически :
> length $ primes 10000 -- => 1229 (0.50 secs)
> length $ primes 20000 -- => 2262 (1.40 secs)
> logBase (2262/1229) (1.4/0.5) -- => 1.6878 ~ n^1.69
> length $ primes 40000 -- => 4203 (4.07 secs)
> logBase (4203/2262) (4.07/1.4) -- => 1.7225 ~ n^1.72
Оптимальное пробное деление обычно выполняется при ~ n 1.4..1.45 , на основе списка сито Эратосфена в ~ n 1.2..1.25 и, если это оптимально реализовано на изменяемых массивах, при ~ n 1.0..1.1 (с n количество произведенных простых чисел, не верхний предел).