Как работает Clojure ^: const?
Я пытаюсь понять, что делает ^:const
в clojure. Об этом говорит разработчик. http://dev.clojure.org/display/doc/1.3
(def константы {: pi 3.14: e 2.71})
(def ^: const pi (: pi константы)) (def ^: const e (: e константы))
Накладные расходы: e и: pi на карте происходит во время компиляции, поскольку (: pi constants) и (: e константы) оцениваются при оценке их родительских форм def.
Это сбивает с толку, поскольку метаданные для символа var, привязанного к символу pi
, и var, привязанного к символу e
, но нижеследующее предложение говорит, что это помогает ускорить поиск по карте, а не поиск в var.
Может кто-нибудь объяснить, что делает ^:const
и обоснование использования? Как это сравнивается с использованием гигантского блока let
или с использованием макроса типа (pi)
и (e)
?
Ответы
Ответ 1
Это выглядит как плохой пример для меня, поскольку материал о поиске по карте просто путает проблему.
Более реалистичным примером может быть:
(def pi 3.14)
(defn circumference [r] (* 2 pi r))
В этом случае тело окружности скомпилировано в код, где разыгрываются pi во время выполнения (вызывая Var.getRawRoot), каждый раз, когда вызывается окружность.
(def ^:const pi 3.14)
(defn circumference [r] (* 2 pi r))
В этом случае окружность компилируется в точно такой же код, как если бы она была написана следующим образом:
(defn circumference [r] (* 2 3.14 r))
Таким образом, вызов Var.getRawRoot пропускается, что экономит немного времени. Вот быстрое измерение, где circ - первая версия выше, а circ2 - вторая:
user> (time (dotimes [_ 1e5] (circ 1)))
"Elapsed time: 16.864154 msecs"
user> (time (dotimes [_ 1e5] (circ2 1)))
"Elapsed time: 6.854782 msecs"
Ответ 2
В примере docs они пытаются показать, что в большинстве случаев, если вы def
var должны быть результатом поиска чего-то на карте без использования константы, тогда поиск будет выполняться при загрузке класса. , поэтому вы платите стоимость один раз при каждом запуске программы (не при каждом поиске, просто при загрузке класса). И оплачивайте стоимость поиска значения в var каждый раз, когда он читается.
Если вы вместо этого сделаете его const, тогда компилятор будет предварительно запрограммировать поиск во время компиляции, а затем испустит простую переменную java и вы заплатите стоимость поиска только один раз во время компиляции программа.
Это надуманный пример, потому что один поиск карты в момент загрузки класса и некоторые поисковые запросы во время выполнения в основном ничего не представляют, хотя это иллюстрирует тот факт, что некоторые работы происходят во время компиляции, некоторые - во время загрузки, а остальные - хорошо. остальное время
Ответ 3
Помимо аспекта эффективности, описанного выше, существует также аспект безопасности, который также полезен. Рассмотрим следующий код:
(def two 2)
(defn times2 [x] (* two x))
(assert (= 4 (times2 2))) ; Expected result
(def two 3) ; Ooops! The value of the "constant" changed
(assert (= 6 (times2 2))) ; Used the new (incorrect) value
(def ^:const const-two 2)
(defn times2 [x] (* const-two x))
(assert (= 4 (times2 2))) ; Still works
(def const-two 3) ; No effect!
(assert (= 3 const-two )) ; It did change...
(assert (= 4 (times2 2))) ; ...but the function did not.
Итак, используя метаданные ^: const при определении варов, вары эффективно "встроены" в каждое место, где они используются. Поэтому любые последующие изменения в var не влияют на какой-либо код, в котором "старое" значение уже было вложено.
Использование функции ^: const также служит для функции документации. Когда вы читаете (def ^: const pi 3.14159), он сообщает читателю, что var pi никогда не предназначается для изменения, что это просто удобное (и, надеюсь, описательное) имя для значения 3.14159.
Сказав вышеизложенное, обратите внимание, что я никогда не использую ^:const
в своем коде, поскольку он обманчив и предоставляет "ложную гарантию", что var никогда не изменится. Проблема в том, что ^:const
подразумевает, что нельзя переопределить var, но, как мы видели с помощью const-two
, это не мешает изменению var. Вместо этого ^:const
скрывает тот факт, что var имеет новое значение, поскольку const-two
было скопировано/включено (во время компиляции) в каждое место использования до изменения var (во время выполнения).
Лучшим решением было бы выбросить исключение при попытке изменить ^:const
var.