Подтвердить что ты не робот

Как работает 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)?

4b9b3361

Ответ 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.