Существуют ли в Clojure ленивые переменные?

У меня есть несколько вычислений, которые несколько дороги (начиная с базы данных), и я только хочу создать базу данных, если я действительно использую ее. Я ищу ссылочную переменную (или просто переменную, если это возможно), которая будет оценивать ее значение только в том случае, если она используется (или разыменована). Что-то концептуально похоже на следующее.

(def v (lazy-var (fn [] (do (println "REALLY EXPENSIVE FUNCTION") true))))

и в будущем, когда я либо просто использую var v, либо вызываю @v, я затем получаю его для распечатки "ДЕЙСТВИТЕЛЬНО ДОРОГОЙ ФУНКЦИИ", а оттуда v имеет значение true. Важно то, что fn не оценивалось до тех пор, пока переменная не была (de) указана. При необходимости функция оценивается один раз и только один раз для вычисления значения переменной. Возможно ли это в clojure?

Ответ 1

delay будет идеально подходит для этого приложения:

delay - (delay & body)

Получает тело выражений и дает объект Delay, который будет вызывать тело только в первый раз, когда он будет принудительно (с помощью force или deref/@), и будет кэшировать результат и возвращать его на всех последующие вызовы force.

Поместите код для создания дескриптора базы данных внутри тела вызова delay, сохраненного в виде Var. Затем разыщите этот Var каждый раз, когда вам нужно использовать дескриптор DB - при первом разыменовании тело будет запущено, а при последующих разборах будет возвращен кэшированный дескриптор.

(def db (delay (println "DB stuff") x))

(select @db ...) ; "DB stuff" printed, x returned
(insert @db ...) ; x returned (cached)

Ответ 2

Clojure 1.3 введена функция memoize для этой цели:

(memoize f)

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

В вашем примере замените несуществующий lazy-var memoize:

(def v (memoize (fn [] (do (println "REALLY EXPENSIVE FUNCTION") true))))
(v)
=>REALLY EXPENSIVE FUNCTION
=>true
(v)
=>true

(delay expr) также выполняет задание, как объясняет другой ответ. Дополнительный комментарий о разыменовании задержки - разница между силой и deref/@заключается в том, что сила не генерирует исключение, если используется для переменной non-delay, в то время как deref/@может бросать ClassCastException "не может быть отлита до clojure.lang.IDeref".