2012-02-06 17 views
37

Próbuję zrozumieć, co robi ^:const w clojure. Tak mówią doktorzy Dev. http://dev.clojure.org/display/doc/1.3Jak działa Clojure ^: const?

(def stałe {PI 3,14 : E 2,71}) ​​

(pok^const PI (: Stałe PI)) (pok^const e (: e stałe))

Narzut szukania w górę: ei: pi na mapie odbywa się w czasie kompilacji, jako że (: stałe pi) i (: e stałe) są oceniane, gdy ich macierzyste formy def są oceniane.

Jest to mylące, ponieważ metadane dla var związany z symbolem pi, a zmienna związana z symbolem e, jeszcze niżej zdanie mówi, że pomaga przyspieszyć wyszukiwań map, a nie wyszukiwań var.

Czy ktoś może wyjaśnić, co robi ^:const i uzasadnienie jego użycia? Jak to porównać z użyciem gigantycznego bloku let lub używając makra takiego jak (pi) i (e)?

Odpowiedz

56

To wygląda mi na złym przykładzie, ponieważ rzeczy na temat wyszukiwania mapy tylko myli problem.

Bardziej realistyczne przykład to być:

(def pi 3.14) 
(defn circumference [r] (* 2 pi r)) 

W tym przypadku korpus obwodu opracowano do kodu, który w czasie wykonywania dereferences PI (wywołując Var.getRawRoot), każdy obwód czasu jest tzw.

(def ^:const pi 3.14) 
(defn circumference [r] (* 2 pi r)) 

W tym przypadku obwód jest kompilowany do dokładnie tego samego kodu, jakby zostały napisane tak:

(defn circumference [r] (* 2 3.14 r)) 

Oznacza to, że wezwanie do Var.getRawRoot jest pomijany, co pozwala zaoszczędzić trochę czasu. Oto szybki pomiar, gdzie Circ to pierwsza wersja powyżej, a circ2 to drugi:

user> (time (dotimes [_ 1e5] (circ 1))) 
"Elapsed time: 16.864154 msecs" 
user> (time (dotimes [_ 1e5] (circ2 1))) 
"Elapsed time: 6.854782 msecs" 
+0

Czy to oznacza, że ​​następujące (def ^: const klucz-na-num {: jeden 1: dwa 2}) (def sum (+ (: jeden klawisz-na-num) (: dwa klucz do-do- num)) jest kompilowany do (def Podsumowując (+ 1 2)) –

+0

nie, ^:? const działa tylko na wartościach pierwotnych, a nie dowolnych obiektów – noisesmith

9

W przykładowych dokumentach starają się pokazać, że w większości przypadków, jeśli def var jest wynikiem wyszukiwania czegoś na mapie bez użycia const, wówczas wyszukiwanie nastąpi, gdy klasa się załaduje. , więc płacisz koszt za każdym razem, gdy uruchamiasz program (nie przy każdym wyszukiwaniu, tylko po załadowaniu klasy). I zapłać koszt szukania wartości w var za każdym razem, gdy zostanie odczytany.

Jeśli zamiast zrobić to const następnie kompilator preform odnośnika w czasie kompilacji, a następnie emitują zmienną ostateczną prosty Java i zapłacisz koszt odnośnika tylko raz łączną w momencie kompilacji programu.

Jest to przykładowy przykład, ponieważ jedno przeszukiwanie mapy przy czasie ładowania klasy i niektóre wyszukiwania var w środowisku wykonawczym są w zasadzie niczym, chociaż ilustruje to, że niektóre prace mają miejsce podczas kompilacji, niektóre w czasie ładowania, a reszta dobrze. .. reszta czasu

+2

Nie sądzę, że to jest dokładne. Ważniejsze oszczędności to nie wyszukiwanie mapy, ale wyszukiwanie zmiennych dla 'pi' i' e', które pojawią się za każdym razem, gdy odwołasz się do któregokolwiek z nich, jeśli brakuje '^: const', ale nie występuje przy all, gdy dołączone jest '^: const'. – amalloy

+0

Dzięki za wskazanie, że jestem edytowany, aby dodać również var lookup. –

8

Oprócz aspektu efektywności opisanej powyżej, jest aspekt bezpieczeństwa, który jest również przydatna.Rozważmy następujący kod:

(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. 

Tak, za pomocą ^: const metadane przy definiowaniu Vars Vars te są skutecznie „inline” w każdym miejscu są używane. Wszelkie późniejsze zmiany w var nie mają zatem wpływu na żaden kod, w którym "stara" wartość została już wstawiona.

Użycie funkcji ^: const służy również do funkcji dokumentacji. Kiedy czyta się (def ^: const pi 3.14159) mówi czytelnikowi, że var pi nie ma nigdy zamiaru zmieniać, że jest po prostu wygodną (& mam nadzieję opisową) nazwą dla wartości 3.14159.

Powiedziawszy powyżej, zauważ, że nigdy nie używam ^:const w moim kodzie, ponieważ jest zwodniczy i zapewnia "fałszywą pewność", że var nigdy się nie zmieni. Problem polega na tym, że ^:const sugeruje, że nie można przedefiniować var, ale jak widzieliśmy z const-two, to nie zapobiega zmianie var. Zamiast tego, ^:const ukrywa fakt, że zmienna ma nową wartość, ponieważ const-two została skopiowana/wstawiona (podczas kompilacji) do każdego miejsca użycia przed zmianą var (w czasie wykonywania).

Znacznie lepszym rozwiązaniem byłoby wyrzucenie wyjątku przy próbie zmiany ^:const var.

+1

[Clojure Style Guide] (https: // github. com/bbatsov/clojure-style-guide # naming) mówi: "Nie używaj specjalnej notacji dla stałych, wszystko przyjmuje stałą, chyba że podano inaczej." Czy to sugeruje, że wartości są rutynowo inline, przynajmniej wtedy, gdy nie są ponownie -'def'ed? (Może '^: const' oznacza tylko" może być wbudowane, nawet jeśli istnieje późniejsza "def" tej zmiennej ", ale jeśli nie ma późniejszej" def ", nawet nie-^: const'ed vars mig Zostajesz przygotowany? Moje bardzo dzikie domysły.) Lub jeśli zmienne '^: const'ed są różne, dlaczego nie mam tego wyrazić, nazywając np. '+ name +' jak w Common Lisp. – Mars

+0

Używanie ^: const ma 2 efekty. (1) jest to, że kompilator będzie inline wartość i nie użyje var, więc jesteś odporny na zmiany lub nadużycie var. (2) to po prostu dokumentacja; autor twierdzi, że wartość * nigdy * nigdy się nie zmienia. –

+0

@Mars Uważam, że przewodnik po stylach oznacza, że ​​warianty należy przyjmować jako stałe, ponieważ w Clojure nie zaleca się zmiany wartości głównej Var po jej ustawieniu. Zmieniony Var, normalnie będzie dynamicznym Varem i otoczony przez nauszniki. Tak więc "nazwa" będzie przyjęta jako stała (ponieważ nigdy się nie zmieni, nie tak jak w optymalizacji lub gwarancji kompilatora), a "* nazwa *" będzie przyjęta jako nie stała. Natomiast '^: const' jest po prostu wskazaniem kompilatorowi, że powinien zoptymalizować kod przez zaznaczenie użycia var. –

Powiązane problemy