Существуют ли в 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".