10.6 Отложенная и ленивая оценка

Иногда полезно отложить оценку выражения, например, если вы хотите избежать выполнения длительного вычисления, если в дальнейшем в программе окажется, что результат не нужен. Библиотека thunk предоставляет следующие функции и макросы для поддержки такой отложенной оценки:

Макрос: thunk-delay forms…

Возвращает thunk для оценки forms. Thunk — это замыкание (см. Closures), которое наследует лексическую среду вызова thunk-delay. Использование этого макроса требует lexical-binding.

Функция: thunk-force thunk

Принудительно выполняет thunk оценку форм, указанных в thunk-delay, который создал thunk. Возвращается результат оценки последней формы. thunk также «запоминает», что он был принужден: любые дальнейшие вызовы thunk-force с тем же thunk просто вернут тот же результат, не выполняя формы повторно.

Макрос: thunk-let (bindings…) forms…

Этот макрос аналогичен let, но создает «ленивые» привязки переменных. Каждая привязка имеет вид (symbol value-form). В отличие от let, оценка любой value-form откладывается до первого использования привязки соответствующего symbol при оценке forms. Любая value-form оценивается не более одного раза. Использование этого макроса требует lexical-binding.

Пример:

(defun f (number)
  (thunk-let ((derived-number
              (progn (message "Calculating 1 plus 2 times %d" number)
                     (1+ (* 2 number)))))
    (if (> number 10)
        derived-number
      number)))

(f 5)
⇒ 5

(f 12)
-| Calculating 1 plus 2 times 12
⇒ 25

Из-за особого характера лениво привязанных переменных, является ошибкой их установка (например, с помощью setq).

Макрос: thunk-let* (bindings…) forms…

Это похоже на thunk-let, но любое выражение в bindings может ссылаться на предыдущие привязки в этой форме thunk-let*. Использование этого макроса требует lexical-binding.

(thunk-let* ((x (prog2 (message "Calculating x...")
                    (+ 1 1)
                  (message "Finished calculating x")))
             (y (prog2 (message "Calculating y...")
                    (+ x 1)
                  (message "Finished calculating y")))
             (z (prog2 (message "Calculating z...")
                    (+ y 1)
                  (message "Finished calculating z")))
             (a (prog2 (message "Calculating a...")
                    (+ z 1)
                  (message "Finished calculating a"))))
  (* z x))

-| Calculating z...
-| Calculating y...
-| Calculating x...
-| Finished calculating x
-| Finished calculating y
-| Finished calculating z
⇒ 8

thunk-let и thunk-let* неявно используют thunks: их расширение создает вспомогательные символы и связывает их с thunks, оборачивающими выражения привязки. Все ссылки на исходные переменные в теле forms затем заменяются выражением, которое вызывает thunk-force с соответствующей вспомогательной переменной в качестве аргумента. Таким образом, любой код, использующий thunk-let или thunk-let*, можно переписать с использованием thunks, но во многих случаях использование этих макросов приводит к более чистому коду, чем явное использование thunks.