Я знаю, что уже немного поздно, и ответ уже принят. Тем не менее, я считаю, что хорошее пошаговое руководство по созданию чего-то рекурсивного может быть интересным для ОП или кого-либо еще в этом отношении. Вот несколько советов, которые, безусловно, помогли мне. Я собираюсь использовать прямой пример, отличный от простого поколения, потому что, как утверждали другие, есть более эффективные способы генерирования простых чисел.
Рассмотрим наивную реализацию функции count, которая создаст список целых чисел, отсчитывающих от некоторого n
. Эта версия не является хвостовой рекурсией, поэтому для длинных списков вы встретите исключение переполнения стека:
let rec countDown = function
| 0 -> []
| n -> n :: countDown (n - 1)
(* ^
|... the cons operator is in the tail position
as such it is evaluated last. this drags
stack frames through subsequent recursive
calls *)
Один из способов исправить это - применить стиль передачи продолжения с параметризованной функцией:
let countDown' n =
let rec countDown n k =
match n with
| 0 -> k [] (* v--- this is continuation passing style *)
| n -> countDown (n - 1) (fun ns -> n :: k ns)
(* ^
|... the recursive call is now in tail position *)
countDown n (fun ns -> ns)
(* ^
|... and we initialize k with the identity function *)
Затем преобразуйте эту параметризованную функцию в специализированное представление. Обратите внимание, что функция countDown'
на самом деле не ведет обратный отсчет. Это артефакт способа построения продолжения при n > 0
, а затем оценки при n = 0
. Если у вас есть что-то похожее на первый пример, и вы не можете понять, как сделать его рекурсивным, я предлагаю вам написать второй, а затем попытаться оптимизировать его, чтобы исключить параметр функции k
. Это, безусловно, улучшит читабельность. Это оптимизация второго примера:
let countDown'' n =
let rec countDown n ns =
match n with
| 0 -> List.rev ns (* reverse so we are actually counting down again *)
| n -> countDown (n - 1) (n :: ns)
countDown n []