(Обновлено в ответ на комментарий ОП; см. Новый раздел ниже исходного ответа.)
Используйте список для стека и сделайте его одной из переменных цикла. Э.Г.
(let loop ((stack (list))
... ; other loop variables here,
; like e.g. what remains of the infix expression
)
... ; loop body
)
Затем, когда вы захотите изменить то, что находится в стеке на следующей итерации, ну, в общем, просто сделайте это.
(loop (cons 'foo stack) ...)
Также обратите внимание, что если вам нужно выполнить несколько «последовательных» обновлений, вы часто можете смоделировать это с помощью формы let*
. На самом деле это не работает с векторами в Scheme (хотя оно работает с постоянными векторами Clojure, если вы хотите посмотреть на них), но оно работает со скалярными значениями и списками, а также с потоками SRFI 40/41.
В ответ на ваш комментарий о том, что петли исключены как «императивная» функция:
(let loop ((foo foo-val)
(bar bar-val))
(do-stuff))
является синтаксическим сахаром для
(letrec ((loop (lambda (foo bar) (do-stuff))))
(loop foo-val bar-val))
letrec
затем расширяется до формы let
, которая, вероятно, будет использовать что-то эквивалентное set!
или локальному define
внутри, но считается совершенно функциональным. Кстати, вы можете использовать другой символ вместо loop
. Кроме того, этот тип let
называется «именованным let
» (или иногда «помеченным»).
Вы, вероятно, помните, что базовая форма let
:
(let ((foo foo-val)
(bar bar-val))
(do-stuff))
также является синтаксическим сахаром при умном использовании lambda
:
((lambda (foo bar) (do-stuff)) foo-val bar-val)
так что все сводится к применению процедуры, как обычно на схеме.
Именованные let
делает самовосстановление красивее, вот и все; и, как я уверен, вы уже знаете, (само) рекурсия с хвостовыми вызовами - это путь, который нужно использовать при функциональном моделировании итерационных вычислительных процессов .
Понятно, что эта конкретная "зацикленная" конструкция прекрасно подходит и для императивного программирования - просто используйте set!
или мутаторы структуры данных в теле цикла, если это то, что вы хотите сделать, - но если вы держитесь подальше от деструктивной функции в вызовах, нет ничего обязательного в циклической рекурсии или в теге let
. Фактически, циклическая рекурсия является одним из самых основных приемов в функциональном программировании, и весь смысл этого вида домашней работы должен был бы научить именно этому ...: -)
Если вы действительно не уверены в том, можно ли его использовать (или будет достаточно ясно, что вы понимаете шаблон, если вы просто используете имя let
), то вы можете просто удалить его, как описано выше ( возможно, используя локальный define
вместо letrec
).