Как циклы реализованы в функциональных языках

На функциональных языках, таких как Scheme или же Lisp существуют for а также for-all петли. тем не мение for циклы требуют мутации, так как это не новый кадр стека на каждой итерации. Поскольку мутация не доступна в этих языках явно, как эти функциональные языки реализуют свои соответствующие итеративные циклы?

2 ответа

Схемы циклов реализованы с использованием рекурсии под капотом; такие конструкции, как do это просто макросы, которые переводятся в рекурсивные процедуры. Например, этот цикл на типичном процедурном языке:

void print(int n) {
    for (int i = 0; i < n; i++) {
        display(i);
    }
}

... эквивалентно следующей процедуре на схеме; здесь вы можете видеть, что каждая часть цикла (инициализация, условие выхода, приращение, тело) имеет соответствующее выражение:

(define (print n)
  (define (loop i)     ; helper procedure, a "named let" would be better
    (when (< i n)      ; exit condition, if this is false the recursion ends
      (display i)      ; body
      (loop (+ i 1)))) ; increment
  (loop 0))            ; initialization

Вы заметили, что после вызова рекурсии больше нечего делать? компилятор достаточно умен, чтобы оптимизировать его для использования одного фрейма стека, что делает его for цикл - читайте о хвостовой рекурсии для более подробной информации. И просто чтобы уточнить, в Схеме мутация явно доступна, читайте о set! инструкция.

Этот вопрос действительно два вопроса, и путаница.

Итерация в схеме

В Схеме итерация реализуется путем рекурсии вместе с семантикой языка, предписывающей, что определенные виды рекурсии не потребляют память, в частности хвостовую рекурсию. Обратите внимание, что это не подразумевает мутации. Так, например, вот определение while петля я ракетка.

(define-syntax-rule (while test form ...)
  (let loop ([val test])
    (if val
        (begin
          form ...
          (loop test))
        (void))))

Как вы можете видеть рекурсивный вызов loop находится в положении хвоста и поэтому не потребляет памяти.

Итерация в традиционных Лиспах

Традиционные Лиспы не требуют устранения хвостовых вызовов и, следовательно, требуют итеративных конструкций: они обычно предоставляются языком, но обычно могут быть реализованы в терминах конструкций более низкого уровня, таких как GO TO. Вот определение while в Common Lisp, который делает это:

(defmacro while (test &body forms)
  (let ((lname (make-symbol "LOOP")))
    `(tagbody
      ,lname
      (if ,test
          (progn
            ,@forms
            (go ,lname))))))

Путаница по поводу мутации

Как в Scheme, так и в традиционных Лиспах предусмотрены операторы мутации: ни один из них не является чисто функциональными языками. Схема ближе к тому, чтобы быть единым целым, но она все еще не очень близка.

Другие вопросы по тегам