Как работает Clojure ^: const? - PullRequest
       15

Как работает Clojure ^: const?

40 голосов
/ 06 февраля 2012

Я пытаюсь понять, что ^:const делает в ближайшем будущем. Это то, что говорят разработчики документации. http://dev.clojure.org/display/doc/1.3

(постоянные определения {: пи 3.14 : e 2.71})

(def ^: const pi (: pi-константы)) (def ^: const e (: e константы))

Затраты на поиск: e и: pi на карте происходят во время компиляции, поскольку (: константы pi) и (: константы e) оцениваются при оценке их родительских форм def.

Это сбивает с толку, поскольку метаданные предназначены для переменной, связанной с символом pi, и переменной, связанной с символом e, однако в приведенном ниже предложении говорится, что это помогает ускорить поиск по карте, а не поиск по переменной.

Может кто-нибудь объяснить, что делает ^:const, и обоснование его использования? Как это можно сравнить с использованием гигантского блока let или макросов вроде (pi) и (e)?

Ответы [ 3 ]

66 голосов
/ 06 февраля 2012

Это выглядит как плохой пример для меня, так как материал о поиске по карте просто запутывает проблему.

Более реалистичный пример:

(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"
10 голосов
/ 06 февраля 2012

В примерах документов они пытаются показать, что в большинстве случаев, если вы def var - результат поиска чего-либо на карте без использования const, тогда поиск произойдет, когда класс загрузится. , поэтому вы оплачиваете стоимость один раз при каждом запуске программы (не при каждом поиске, а только при загрузке класса). И оплачивайте стоимость поиска значения в переменной каждый раз, когда она читается.

Если вместо этого вы сделаете его константным, то компилятор преформирует поиск во время компиляции, а затем выдаст простую конечную переменную java, и вы заплатите стоимость поиска только один раз всего во время компиляции программы. .

Это надуманный пример, потому что один просмотр карты во время загрузки класса и несколько поисков var во время выполнения в основном ничего не значат, хотя он иллюстрирует момент, когда некоторая работа выполняется во время компиляции, некоторые во время загрузки, а остальные хорошо .. остальное время

8 голосов
/ 09 февраля 2014

Помимо аспекта эффективности, описанного выше, есть аспект безопасности, который также полезен. Рассмотрим следующий код:

(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 в своем коде, так как это обманчиво и дает "ложную гарантию", что переменная никогда не изменится. Проблема в том, что ^:const подразумевает, что никто не может переопределить переменную, но, как мы видели с const-two, это не препятствует изменению переменной. Вместо этого ^:const скрывает тот факт, что переменная имеет новое значение, поскольку const-two была скопирована / вставлена ​​(во время компиляции) в каждое место использования до изменения переменной (во время выполнения).

Гораздо лучшим решением было бы создание исключения при попытке изменить ^:const var.

...