Wörtliche Oktale
Irgendwann las ich in einer Matrix, in der führende Nullen verwendet wurden, um die richtigen Zeilen und Spalten zu erhalten. Mathematisch ist dies richtig, da die führende Null den zugrunde liegenden Wert offensichtlich nicht ändert. Versuche, eine Var mit dieser Matrix zu definieren, würden jedoch auf mysteriöse Weise scheitern mit:
java.lang.NumberFormatException: Invalid number: 08
das hat mich total verblüfft. Der Grund dafür ist, dass Clojure Literal-Integer-Werte mit führenden Nullen als Oktale behandelt und es keine Nummer 08 im Oktal gibt.
Ich sollte auch erwähnen, dass Clojure traditionelle Java-Hexadezimalwerte über das Präfix 0x unterstützt . Sie können auch eine beliebige Basis zwischen 2 und 36 verwenden, indem Sie die Notation "base + r + value" verwenden, z. B. 2r101010 oder 36r16 (42 Basis zehn).
Es wird versucht, Literale in einem anonymen Funktionsliteral zurückzugeben
Das funktioniert:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
Also glaubte ich, dass dies auch funktionieren würde:
(#({%1 %2}) :a 1)
aber es scheitert mit:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
weil das # () Reader-Makro auf erweitert wird
(fn [%1 %2] ({%1 %2}))
mit dem Kartenliteral in Klammern. Da es das erste Element ist, wird es als Funktion behandelt (was eine Literal-Map tatsächlich ist), es werden jedoch keine erforderlichen Argumente (z. B. ein Schlüssel) bereitgestellt. Zusammenfassend lässt sich sagen, dass das anonyme Funktionsliteral nicht erweitert wird
(fn [%1 %2] {%1 %2}) ; notice the lack of parenthesis
Daher können Sie keinen Literalwert ([] ,: a, 4,%) als Hauptteil der anonymen Funktion haben.
In den Kommentaren wurden zwei Lösungen angegeben. Brian Carper schlägt vor, Sequenzimplementierungskonstruktoren (Array-Map, Hash-Set, Vektor) wie folgt zu verwenden:
(#(array-map %1 %2) :a 1)
während Dan zeigt, dass Sie die Identitätsfunktion verwenden können, um die äußere Klammer zu entpacken:
(#(identity {%1 %2}) :a 1)
Brians Vorschlag bringt mich tatsächlich zu meinem nächsten Fehler ...
Der Gedanke, dass Hash-Map oder Array-Map die unveränderliche konkrete Map-Implementierung bestimmen
Folgendes berücksichtigen:
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
Während Sie sich im Allgemeinen nicht um die konkrete Implementierung einer Clojure-Karte kümmern müssen, sollten Sie wissen, dass Funktionen, die eine Karte vergrößern - wie assoc oder conj -, eine PersistentArrayMap verwenden und eine PersistentHashMap zurückgeben können , die bei größeren Karten eine schnellere Leistung erbringt.
Verwenden einer Funktion als Rekursionspunkt anstelle einer Schleife , um anfängliche Bindungen bereitzustellen
Als ich anfing, schrieb ich viele Funktionen wie diese:
; Project Euler #3
(defn p3
([] (p3 775147 600851475143 3))
([i n times]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
In der Tat wäre die Schleife für diese bestimmte Funktion prägnanter und idiomatischer gewesen:
; Elapsed time: 387 msecs
(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))))
Beachten Sie, dass ich das leere Argument "Standardkonstruktor" -Funktionskörper (p3 775147 600851475143 3) durch eine Schleife + anfängliche Bindung ersetzt habe. Die RECUR nun erneut bindet die Loop - Bindungen (anstelle der Parameter fn) und springt zurück zum Rekursion Punkt (Schleife statt fn).
Referenzieren von "Phantom" -Vars
Ich spreche über die Art von var, die Sie mithilfe der REPL definieren könnten - während Ihrer explorativen Programmierung - und dann unwissentlich in Ihrer Quelle referenzieren. Alles funktioniert einwandfrei, bis Sie den Namespace neu laden (möglicherweise durch Schließen Ihres Editors) und später eine Reihe ungebundener Symbole entdecken, auf die im gesamten Code verwiesen wird. Dies tritt auch häufig auf, wenn Sie ein Varactoring durchführen und eine Variable von einem Namespace in einen anderen verschieben.
Behandeln Sie das For- Listen-Verständnis wie einen Imperativ für die Schleife
Im Wesentlichen erstellen Sie eine Lazy-Liste basierend auf vorhandenen Listen, anstatt einfach eine Regelschleife auszuführen. Clojure der doseq ist eigentlich mehr analog Imperativ foreach Schleifenkonstrukte.
Ein Beispiel dafür, wie unterschiedlich sie sind, ist die Möglichkeit, mithilfe beliebiger Prädikate zu filtern, über welche Elemente sie iterieren:
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)
Eine andere Art, wie sie sich unterscheiden, ist, dass sie mit unendlich faulen Sequenzen arbeiten können:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
Sie können auch mehr als einen Bindungsausdruck verarbeiten, wobei sie zuerst über den Ausdruck ganz rechts iterieren und sich nach links arbeiten:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Es gibt auch keine Pause oder weiterhin vorzeitig zu beenden.
Überbeanspruchung von Strukturen
Ich komme aus einem OOPish-Umfeld. Als ich mit Clojure anfing, dachte mein Gehirn immer noch an Objekte. Ich fand mich dabei, alles als Struktur zu modellieren, weil ich mich durch die Gruppierung von "Mitgliedern", so locker sie auch sein mögen, wohl fühlte. In der Realität sollten Strukturen meist als Optimierung betrachtet werden. Clojure teilt die Schlüssel und einige Suchinformationen, um Speicherplatz zu sparen. Sie können sie weiter optimieren, indem Sie Accessoren definieren , um die Suche nach Schlüsseln zu beschleunigen.
Insgesamt erhalten Sie durch die Verwendung einer Struktur über einer Karte nichts außer der Leistung, sodass sich die zusätzliche Komplexität möglicherweise nicht lohnt.
Verwenden nicht empfohlener BigDecimal-Konstruktoren
Ich brauchte viele BigDecimals und schrieb so hässlichen Code:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
Tatsächlich unterstützt Clojure BigDecimal-Literale, indem M an die Zahl angehängt wird :
(= (BigDecimal. "42.42") 42.42M) ; true
Durch die Verwendung der gezuckerten Version wird ein Großteil des Aufblähens vermieden. In den Kommentaren erwähnte Twils , dass Sie auch die Funktionen bigdec und bigint verwenden können, um expliziter zu sein, aber dennoch präzise bleiben.
Verwenden der Namenskonvertierungen für Java-Pakete für Namespaces
Dies ist eigentlich kein Fehler an sich, sondern etwas, das gegen die idiomatische Struktur und Benennung eines typischen Clojure-Projekts verstößt. Mein erstes umfangreiches Clojure-Projekt hatte Namespace-Deklarationen - und entsprechende Ordnerstrukturen - wie folgt:
(ns com.14clouds.myapp.repository)
was meine vollqualifizierten Funktionsreferenzen aufgebläht hat:
(com.14clouds.myapp.repository/load-by-name "foo")
Um die Sache noch komplizierter zu machen, habe ich eine Standard- Maven- Verzeichnisstruktur verwendet:
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
Das ist komplexer als die "Standard" Clojure-Struktur von:
|-- src/
|-- test/
|-- resources/
Dies ist die Standardeinstellung von Leiningen- Projekten und Clojure selbst.
Karten verwenden Javas equals () anstelle von Clojures = für die Schlüsselübereinstimmung
Ursprünglich von Chouser im IRC gemeldet , führt diese Verwendung von Javas equals () zu einigen nicht intuitiven Ergebnissen:
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
Da sowohl Integer- als auch Long- Instanzen von 1 standardmäßig gleich gedruckt werden, kann es schwierig sein, festzustellen, warum Ihre Karte keine Werte zurückgibt. Dies gilt insbesondere dann, wenn Sie Ihren Schlüssel durch eine Funktion übergeben, die, möglicherweise ohne Ihr Wissen, eine lange Zeit zurückgibt.
Es ist zu beachten, dass die Verwendung von Java's equals () anstelle von Clojure's = wichtig ist, damit Maps der Schnittstelle java.util.Map entsprechen.
Ich verwende Programming Clojure von Stuart Halloway, Practical Clojure von Luke VanderHart und die Hilfe unzähliger Clojure-Hacker im IRC und die Mailingliste, um meine Antworten zu unterstützen.