Одна из проблем с «очевидным» ответом на этот вопрос заключается в том, что он не очень хорошо работает.Подумайте об этом:
(define (gen-list start end)
(if (> start end)
'()
(cons start
(gen-list (+ start 1) end))))
Что ж, если start
намного меньше, чем end
, в стеке будет огромное количество рекурсивных вызовов, потому что это правильно рекурсивная функция: рекурсивнаявызов gen-list
является реальным вызовом и должен быть возвращен до того, как может произойти вызов cons
(который является хвостовым вызовом).
Чтобы справиться с этим, нужно повернуть шаблоны, которые выглядят как (cons x (<recursive-call> ...))
в паттерны, которые выглядят как (<tail-call> ... (cons x ...))
: вам нужна функция с дополнительным аргументом, аккумулятор .Это означает, что вызовы, которые ранее были рекурсивными, теперь являются хвостовыми вызовами, и поэтому все хорошо: процесс теперь итеративный.
Проблема в том, что списки выходят задом наперед (вам нужно подумать, почему это так., но это очевидно после недолгих раздумий).Таким образом, вам нужно полностью изменить результат.К счастью, изменение списка также является итеративным процессом, и это нормально.
Но в этом случае, ну, вы можете просто считать в обратном направлении!Таким образом, простой подход выглядит следующим образом, используя локально определенную вспомогательную функцию (это можно определить как функцию верхнего уровня, но зачем?):
(define (gen-list low high)
(define (gla i result)
(if (< i low)
result
(gla (- i 1) (cons i result))))
(gla high '()))
Вы видите, что это подсчетв обратном направлении: начальный вызов gla
начинается с high
и затем создает список в обратном направлении.Итак, теперь:
> (gen-list 1 3)
'(1 2 3)
Как мы хотим.
Это такой общий шаблон в Scheme, что для него существует специальная конструкция: named let.Таким образом, мы можем перефразировать вышеупомянутое более идиоматическим образом:
(define (gen-list low high)
(let gla ([i high] [result '()])
(if (< i low)
result
(gla (- i 1) (cons i result)))))
Это в точности то же самое, что и предыдущий ответ: он просто перемещает начальный вызов наверх и объединяет его с локальным определением gla
.Вероятно, это идиоматический способ Схемы сделать что-то подобное (хотя люди, которые пишут больше Схем, чем я, могут отличаться: я действительно человек CL и у меня неизбежно плохой вкус).
Вот гдеистория должна закончиться, но я не могу удержаться, добавив следующее.В старые добрые времена comp.lang.lisp люди обычно задавали очевидные домашние вопросы, и, поскольку не было системы кармы, один из подходов заключался в том, чтобы дать ответ, который решил проблему ... будучи абсурдно непрозрачным.
Итак, прежде всего мы можем превратить gla
в функцию, которая передается через продолжение вызова, а не зная, что она должна вызывать сама себя:
(define (gen-list low high)
(let ([gla (λ (cont i result)
(if (< i low)
result
(cont cont (- i 1) (cons i result))))])
(gla gla high '())))
И затем, конечно, мы можем превратить (let ([x y]) ...)
в((λ (x) ...) y)
:
(define (gen-list low high)
((λ (gla)
(gla gla high '()))
(λ (cont i result)
(if (< i low)
result
(cont cont (- i 1) (cons i result))))))
И это хороший, чистый ответ ... который ни один студент не придумает.
Альтернативный подход, который является еще более злым, заключается просто в явномконечно, используйте комбинатор Y.