Я пытаюсь использовать алгоритм «Решето Эратосфена» в Haskell для генерации потока простых чисел, но код, похоже, не работает. Это основная идея.
Основная идея взята из наших лекций по функциональному программированию, которые, в свою очередь, ссылаются на статью Мелиссы О'Нил «Подлинное решето Эратосфена», JFC, 2009, в которой обсуждается, что на самом деле представляет собой решето Эратосфена в Haskell.
union :: Ord a => [a] -> [a] -> [a]
union xs@(x:txs) ys@(y:tys)
| x < y = x:union txs ys
| x > y = y:union xs tys
-- se sono uguali, ne scrivo uno solo
| otherwise = x:union txs tys
minus :: Ord a => [a] -> [a] -> [a]
minus xs@(x:txs) ys@(y:tys)
| x < y = x : minus txs ys
| x > y = y : minus xs tys
| otherwise = x : minus xs ys
unionP :: Ord a => [a] -> [a] -> [a]
unionP (x:xs) ys = x:union xs ys -- poi si va con union
unionAll :: [[Integer]] -> [Integer]
unionAll = foldr1 unionP
primes :: [Integer]
primes = 2:[3..] `minus` composites
where composites = unionAll [map (p*) [p..] | p <-primes]
-- Test
firstPrimes = take 15 primes
Вот что произошло на самом деле, дальше этого дело не идет:
ghci> firstPrimes [2
Поэтому мне пришлось прервать процесс:
ghci> firstPrimes [2^CInterrupted.
ghci>
Похоже, что unionAll
используется для получения бесконечного количества отсортированных списков и создания отсортированного списка их объединения. Я не понимаю, как это может работать, даже с ленью. Чтобы выдать элемент x
, функция должна быть уверена, что первый элемент всех списков больше x
, что невозможно проверить за конечное время. Возможно, вам нужно более сильное предварительное условие (например, список sorted() из бесконечного числа отсортированных списков, где () использует какой-то особый порядок) и более умное unionAll
(foldr1
недостаточно, AFAICS).
Во-первых, ваша реализация minus
неверна. В статье О'Нила используются:
(x:xs) `minus` (y:ys) | x < y = x:(xs `minus` (y:ys))
| x == y = xs `minus` ys
| x > y = (x:xs) `minus` ys
Преобразуя в ваши обозначения, это должно выглядеть так:
minus :: Ord a => [a] -> [a] -> [a]
minus xs@(x:txs) ys@(y:tys)
| x < y = x : minus txs ys
| x > y = minus xs tys
| otherwise = minus txs ys
Во-вторых, в определении union
в статье О'Нила используется foldr
, а не foldr1
, поэтому это эквивалентно:
unionAll = foldr unionP []
вместо:
unionAll = foldr1 unionP
Это может показаться не таким уж большим делом, но простые определения этих функций выглядят так:
foldr f (x:xs) = f x (foldr f xs)
foldr f [] = []
foldr1 f [x] = [x]
foldr1 f (x:xs) = f x (foldr1 f xs)
Видите, как в случае foldr1
аргумент [x]
предшествует аргументу (x:xs)
? Для бесконечного списка ввода регистр [x]
никогда не встречается, но Haskell все равно проверяет регистр [x]
перед обработкой регистра (x:xs)
. Это делает foldr1
немного менее ленивым, чем foldr
.
Например:
λ> head $ foldr unionP [] ([1]:undefined)
1
λ> head $ foldr1 unionP ([1]:undefined)
*** Exception: Prelude.undefined
CallStack (from HasCallStack):
undefined, called at <interactive>:86:27 in interactive:Ghci4
В вашем вызове composites
, где unionAll
вызывается в бесконечном списке списков:
bigList = [map (p*) [p..] | p <- primes]
из-за способа определения foldr1
Haskell на самом деле не может вернуть первый элемент из unionAll bigList
, пока не проверит, что bigList
имеет более одного элемента, но для этого необходимо убедиться, что в списке есть второй элемент:
primes = 2 : ([3..] `minus` composites)
и для этого необходимо проверить, что следующий список не пуст:
[3..] `minus` composites
и для этого необходимо убедиться, что composites
начинается с числа, большего, чем 3
, но для этого требуется оценка:
unionAll bigList
именно с этого начались все вычисления, и вы получили бесконечный цикл.
В любом случае, все, что вам нужно сделать, чтобы это исправить, это заменить:
unionAll = foldr1 unionP
с немного ленивым:
unionAll = foldr unionP []
и это должно работать.
Вот полная рабочая версия вашего кода с исправлениями minus
и unionAll
, которая генерирует простые числа без застревания:
union :: Ord a => [a] -> [a] -> [a]
union xs@(x:txs) ys@(y:tys)
| x < y = x:union txs ys
| x > y = y:union xs tys
| otherwise = x:union txs tys
minus :: Ord a => [a] -> [a] -> [a]
minus xs@(x:txs) ys@(y:tys)
| x < y = x : minus txs ys
| x > y = minus xs tys
| otherwise = minus txs ys
unionP :: Ord a => [a] -> [a] -> [a]
unionP (x:xs) ys = x:union xs ys -- poi si va con union
unionAll :: [[Integer]] -> [Integer]
unionAll = foldr unionP []
primes :: [Integer]
primes = 2:[3..] `minus` composites
where composites = unionAll [map (p*) [p..] | p <- primes]
-- Test
firstPrimes = take 15 primes
Пожалуйста, всегда используйте подписи типов. Кроме того,
2 : [3..]`minus`composited
намного понятнее, чем версия с большим количеством пробелов вокруг оператора с более высоким приоритетом.