Dosłowne ósemki
W pewnym momencie czytałem w macierzy, w której zastosowano zera wiodące, aby zachować właściwe wiersze i kolumny. Matematycznie jest to poprawne, ponieważ wiodące zero oczywiście nie zmienia podstawowej wartości. Próby zdefiniowania zmiennej za pomocą tej macierzy zakończyłyby się jednak tajemniczym niepowodzeniem z:
java.lang.NumberFormatException: Invalid number: 08
co całkowicie mnie zaskoczyło. Powodem jest to, że Clojure traktuje dosłowne liczby całkowite z wiodącymi zerami jako ósemki, aw ósemkowym nie ma liczby 08.
Powinienem również wspomnieć, że Clojure obsługuje tradycyjne wartości szesnastkowe Java poprzez prefiks 0x . Możesz również użyć dowolnej podstawy od 2 do 36, używając notacji „podstawa + r + wartość”, na przykład 2r101010 lub 36r16, które mają 42 podstawy dziesięć.
Próba zwrócenia literałów w anonimowym literale funkcji
To działa:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
więc wierzyłem, że to również zadziała:
(#({%1 %2}) :a 1)
ale zawodzi z:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
ponieważ makro czytnika # () zostaje rozwinięte do
(fn [%1 %2] ({%1 %2}))
z literałem mapy w nawiasach. Ponieważ jest to pierwszy element, jest traktowany jako funkcja (którą w rzeczywistości jest mapa literału), ale nie są dostarczane żadne wymagane argumenty (takie jak klucz). Podsumowując, anonimowy literał funkcji nie rozwija się do
(fn [%1 %2] {%1 %2})
dlatego nie możesz mieć żadnej wartości dosłownej ([],: a, 4,%) jako treści funkcji anonimowej.
W komentarzach podano dwa rozwiązania. Brian Carper sugeruje użycie konstruktorów implementacji sekwencji (array-map, hash-set, vector) w następujący sposób:
(#(array-map %1 %2) :a 1)
podczas gdy Dan pokazuje, że możesz użyć funkcji identity, aby rozpakować zewnętrzny nawias:
(#(identity {%1 %2}) :a 1)
Sugestia Briana doprowadza mnie do kolejnego błędu ...
Myślenie, że mapa hash lub tablica-mapa określa niezmienną implementację konkretnej mapy
Rozważ następujące:
user> (class (hash-map))
clojure.lang.PersistentArrayMap
user> (class (hash-map :a 1))
clojure.lang.PersistentHashMap
user> (class (assoc (apply array-map (range 2000)) :a :1))
clojure.lang.PersistentHashMap
Choć na ogół nie będą musieli się martwić o realizacji betonowej z mapą Clojure, należy wiedzieć, że funkcje, które rosną mapa - jak doc lub powiązaniu - może podjąć PersistentArrayMap i zwracają PersistentHashMap , który wykonuje się szybciej na większych mapach.
Używanie funkcji jako punktu rekurencji zamiast pętli w celu zapewnienia początkowych powiązań
Kiedy zaczynałem, napisałem wiele funkcji, takich jak:
(defn p3
([] (p3 775147 600851475143 3))
([i n times]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Kiedy w rzeczywistości pętla byłaby bardziej zwięzła i idiomatyczna dla tej konkretnej funkcji:
(defn p3 [] {:post [(= % 6857)]}
(loop [i 775147 n 600851475143 times 3]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Zauważ, że zastąpiłem pusty argument, treść funkcji „domyślny konstruktor” (p3 775147 600851475143 3) pętlą + początkowe wiązanie. Powtarzające się teraz na ponowne powiązanie wiązania pętli (a fn parametrami) i powraca do punktu rekursji (pętla zamiast FN).
Nawiązanie do zmiennych „widmowych”
Mówię o typie var, który możesz zdefiniować za pomocą REPL - podczas programowania eksploracyjnego - a następnie nieświadomie odwołać się do źródła. Wszystko działa dobrze, dopóki nie przeładujesz przestrzeni nazw (być może przez zamknięcie edytora), a później odkryjesz kilka niezwiązanych symboli, do których odwołuje się Twój kod. Dzieje się tak również często podczas refaktoryzacji, przenoszenia zmiennej z jednej przestrzeni nazw do drugiej.
Traktowanie rozumienia listy for jako imperatywu pętli
Zasadniczo tworzysz leniwą listę na podstawie istniejących list, zamiast po prostu wykonywać kontrolowaną pętlę. Clojure za doseq jest rzeczywiście bardziej analogiczne do nadrzędnych foreach pętli konstrukcjami.
Jednym z przykładów ich różnic jest możliwość filtrowania, po których elementach iterują, przy użyciu dowolnych predykatów:
user> (for [n '(1 2 3 4) :when (even? n)] n)
(2 4)
user> (for [n '(4 3 2 1) :while (even? n)] n)
(4)
Innym sposobem, w jaki się różnią, jest to, że mogą operować na nieskończonych leniwych sekwencjach:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
Mogą również obsługiwać więcej niż jedno wyrażenie wiążące, iterując najpierw po skrajnym prawym wyrażeniu i działając w lewo:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Jest też bez przerwy lub kontynuować , aby zakończyć przedwcześnie.
Nadużywanie struktur
Pochodzę z OOPish, więc kiedy zacząłem Clojure, mój mózg wciąż myślał w kategoriach przedmiotów. Zacząłem modelować wszystko jako strukturę, ponieważ jej grupowanie „członków”, jakkolwiek luźne, zapewniało mi poczucie komfortu. W rzeczywistości struktury powinny być głównie traktowane jako optymalizacja; Clojure udostępni klucze i niektóre informacje wyszukiwania, aby oszczędzać pamięć. Można dodatkowo zoptymalizować je poprzez zdefiniowanie akcesorów , aby przyspieszyć proces klucza odnośnika.
Ogólnie rzecz biorąc, używanie struktury na mapie nie daje nic poza wydajnością, więc dodatkowa złożoność może nie być tego warta.
Korzystanie z nieugerowanych konstruktorów BigDecimal
Potrzebowałem dużo BigDecimals i pisałem brzydki kod w ten sposób:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
podczas gdy w rzeczywistości Clojure obsługuje literały BigDecimal, dodając M do liczby:
(= (BigDecimal. "42.42") 42.42M)
Używanie wersji z cukrem eliminuje wiele wzdęć. W komentarzach twils wspomniał, że możesz również użyć funkcji bigdec i bigint, aby być bardziej zrozumiałym, ale zachować zwięzłość.
Korzystanie z konwersji nazewnictwa pakietów Java dla przestrzeni nazw
W rzeczywistości nie jest to błąd sam w sobie, ale raczej coś, co jest sprzeczne z idiomatyczną strukturą i nazewnictwem typowego projektu Clojure. Mój pierwszy istotny projekt Clojure miał deklaracje przestrzeni nazw - i odpowiadające im struktury folderów - w ten sposób:
(ns com.14clouds.myapp.repository)
co rozdęło moje w pełni kwalifikowane odwołania do funkcji:
(com.14clouds.myapp.repository/load-by-name "foo")
Aby jeszcze bardziej skomplikować sprawę, użyłem standardowej struktury katalogów Maven :
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
który jest bardziej złożony niż „standardowa” struktura Clojure:
|-- src/
|-- test/
|-- resources/
co jest domyślne dla projektów Leiningen i samego Clojure .
Mapy używają równości () Java zamiast Clojure = do dopasowywania kluczy
Pierwotnie zgłoszone przez chousera na IRC , to użycie metody equals () w Javie prowadzi do pewnych nieintuicyjnych wyników:
user> (= (int 1) (long 1))
true
user> ({(int 1) :found} (int 1) :not-found)
:found
user> ({(int 1) :found} (long 1) :not-found)
:not-found
Ponieważ zarówno Integer, jak i Long instancji 1 są domyślnie drukowane tak samo, może być trudno wykryć, dlaczego mapa nie zwraca żadnych wartości. Jest to szczególnie prawdziwe, gdy przekazujesz klucz przez funkcję, która być może bez Twojej wiedzy zwraca długi.
Należy zauważyć, że przy użyciu Java equals () zamiast Clojure w = ma zasadnicze znaczenie dla mapy aby były zgodne z interfejsem java.util.Map.
Używam Programming Clojure autorstwa Stuarta Hallowaya, Practical Clojure autorstwa Luke VanderHart oraz pomocy niezliczonych hakerów Clojure na IRC i liście mailingowej, aby pomóc w udzieleniu odpowiedzi.