Was sind einige häufige Fehler von Clojure-Entwicklern und wie können wir sie vermeiden?
Beispielsweise; Neulinge bei Clojure denken, dass die contains?
Funktion genauso funktioniert wie java.util.Collection#contains
. Funktioniert jedoch contains?
nur ähnlich, wenn es mit indizierten Sammlungen wie Karten und Sets verwendet wird und Sie nach einem bestimmten Schlüssel suchen:
(contains? {:a 1 :b 2} :b)
;=> true
(contains? {:a 1 :b 2} 2)
;=> false
(contains? #{:a 1 :b 2} :b)
;=> true
Bei Verwendung mit numerisch indizierten Sammlungen (Vektoren, Arrays) wird contains?
nur überprüft, ob das angegebene Element innerhalb des gültigen Indexbereichs liegt (nullbasiert):
(contains? [1 2 3 4] 4)
;=> false
(contains? [1 2 3 4] 0)
;=> true
Wenn eine Liste angegeben wird, contains?
wird niemals true zurückgegeben.
some
Funktion von Clojure verwenden oder, noch besser, sichcontains
selbst verwenden. Clojure-Sammlungen implementierenjava.util.Collection
.(.contains [1 2 3] 2) => true
Antworten:
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:
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:
Also glaubte ich, dass dies auch funktionieren würde:
aber es scheitert mit:
weil das # () Reader-Makro auf erweitert wird
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
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:
während Dan zeigt, dass Sie die Identitätsfunktion verwenden können, um die äußere Klammer zu entpacken:
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:
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:
In der Tat wäre die Schleife für diese bestimmte Funktion prägnanter und idiomatischer gewesen:
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:
Eine andere Art, wie sie sich unterscheiden, ist, dass sie mit unendlich faulen Sequenzen arbeiten können:
Sie können auch mehr als einen Bindungsausdruck verarbeiten, wobei sie zuerst über den Ausdruck ganz rechts iterieren und sich nach links arbeiten:
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:
Tatsächlich unterstützt Clojure BigDecimal-Literale, indem M an die Zahl angehängt wird :
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:
was meine vollqualifizierten Funktionsreferenzen aufgebläht hat:
Um die Sache noch komplizierter zu machen, habe ich eine Standard- Maven- Verzeichnisstruktur verwendet:
Das ist komplexer als die "Standard" Clojure-Struktur von:
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:
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.
quelle
(#(hash-set %1 %2) :a 1)
oder in diesem Fall(hash-set :a 1)
.do
:(#(do {%1 %2}) :a 1)
.hash-map
direkte Verwendung (wie in(hash-map :a 1)
oder(map hash-map keys vals)
) besser lesbar ist und nicht impliziert, dass etwas Besonderes und bisher in einer benannten Funktion nicht implementiert ist findet statt (was die Verwendung von#(...)
impliziert, finde ich). In der Tat ist die Überbeanspruchung anonymer FNS ein Problem, über das man an sich nachdenken muss. :-) OTOH, ich verwende manchmaldo
in sehr präzisen anonymen Funktionen, die frei von Nebenwirkungen sind ... Es ist offensichtlich, dass sie auf einen Blick sind. Eine Frage des Geschmacks, denke ich.Vergessen, die Auswertung von Lazy Seqs zu erzwingen
Lazy Seqs werden nur ausgewertet, wenn Sie sie zur Auswertung auffordern. Sie können erwarten, dass dies etwas druckt, aber es tut es nicht.
Das
map
wird nie ausgewertet, es wird stillschweigend verworfen, weil es faul ist. Sie haben einen von verwendendoseq
,dorun
,doall
usw. Bewertung der faulen Sequenzen für Nebenwirkungen zu erzwingen.Die Verwendung eines Bare
map
bei REPL sieht so aus, als würde es funktionieren, aber es funktioniert nur, weil die REPL die Auswertung von Lazy Seqs selbst erzwingt. Dies kann es noch schwieriger machen, den Fehler zu bemerken, da Ihr Code auf der REPL funktioniert und nicht aus einer Quelldatei oder innerhalb einer Funktion funktioniert.quelle
(map ...)
von innen heraus bewertet(binding ...)
und mich gefragt, warum neue Bindungswerte nicht zutreffen.Ich bin ein Clojure Noob. Fortgeschrittene Benutzer haben möglicherweise interessantere Probleme.
versuchen, unendlich faule Sequenzen zu drucken.
Ich wusste, was ich mit meinen faulen Sequenzen machte, aber zu Debugging-Zwecken fügte ich einige print / prn / pr-Aufrufe ein, nachdem ich vorübergehend vergessen hatte, was ich druckte. Komisch, warum hat mein PC alle aufgelegt?
versuchen, Clojure unbedingt zu programmieren.
Es besteht die Versuchung, eine ganze Menge
ref
s oderatom
s zu erstellen und Code zu schreiben, der ständig mit ihrem Zustand in Konflikt gerät. Dies kann getan werden, aber es passt nicht gut. Es kann auch eine schlechte Leistung haben und selten von mehreren Kernen profitieren.versuchen, Clojure 100% funktional zu programmieren.
Eine Kehrseite dazu: Einige Algorithmen wollen wirklich einen veränderlichen Zustand. Das religiöse Vermeiden eines veränderlichen Zustands um jeden Preis kann zu langsamen oder umständlichen Algorithmen führen. Es braucht Urteilsvermögen und ein bisschen Erfahrung, um die Entscheidung zu treffen.
versuchen, in Java zu viel zu tun.
Da es so einfach ist, Java zu erreichen, ist es manchmal verlockend, Clojure als Wrapper für Skriptsprachen in Java zu verwenden. Natürlich müssen Sie genau dies tun, wenn Sie Java-Bibliotheksfunktionen verwenden, aber es macht wenig Sinn, (z. B.) Datenstrukturen in Java zu verwalten oder Java-Datentypen wie Sammlungen zu verwenden, für die es in Clojure gute Entsprechungen gibt.
quelle
Viele Dinge bereits erwähnt. Ich werde nur noch eine hinzufügen.
Clojure if behandelt Java-Boolesche Objekte immer als true, auch wenn der Wert false ist. Wenn Sie also eine Java-Land-Funktion haben, die einen Java-Booleschen Wert zurückgibt, stellen Sie sicher, dass Sie ihn nicht direkt,
(if java-bool "Yes" "No")
sondern überprüfen(if (boolean java-bool) "Yes" "No")
.Dies hat mich mit der Bibliothek clojure.contrib.sql verbrannt, die boolesche Datenbankfelder als Java-Boolesche Objekte zurückgibt.
quelle
(if java.lang.Boolean/FALSE (println "foo"))
foo nicht gedruckt wird.(if (java.lang.Boolean. "false") (println "foo"))
tut es aber, wohingegen(if (boolean (java.lang.Boolean "false")) (println "foo"))
es nicht ... In der Tat ziemlich verwirrend!nil
undfalse
sind falsch, und alles andere ist wahr. Ein JavaBoolean
ist nichtnil
und es ist nichtfalse
(weil es ein Objekt ist), daher ist das Verhalten konsistent.Halte deinen Kopf in Schleifen.
Es besteht die Gefahr, dass Ihnen der Speicher ausgeht, wenn Sie die Elemente einer möglicherweise sehr großen oder unendlichen, faulen Sequenz durchlaufen und dabei auf das erste Element verweisen.
Vergessen, dass es keine TCO gibt.
Regelmäßige Tail-Calls belegen Stapelspeicher und laufen über, wenn Sie nicht vorsichtig sind. Clojure hat
'recur
und muss'trampoline
viele der Fälle behandeln, in denen optimierte Tail-Calls in anderen Sprachen verwendet werden, aber diese Techniken müssen absichtlich angewendet werden.Nicht ganz faule Sequenzen.
Sie können eine Lazy-Sequenz mit
'lazy-seq
oder'lazy-cons
(oder indem Sie auf Lazy-APIs höherer Ebene aufbauen) erstellen. Wenn Sie sie jedoch einschließen'vec
oder durch eine andere Funktion übergeben, die die Sequenz realisiert, ist sie nicht mehr Lazy. Dadurch können sowohl der Stapel als auch der Heap überflogen werden.Veränderliche Dinge in Refs setzen.
Sie können dies technisch tun, aber nur die Objektreferenz in der Referenz selbst wird vom STM gesteuert - nicht das referenzierte Objekt und seine Felder (es sei denn, sie sind unveränderlich und verweisen auf andere Referenzen). Ziehen Sie es daher nach Möglichkeit vor, nur unveränderliche Objekte in Refs zu verwenden. Gleiches gilt für Atome.
quelle
Verwenden Sie
loop ... recur
, um Sequenzen zu verarbeiten, wenn die Karte ausreicht.vs.
Die Kartenfunktion (im neuesten Zweig) verwendet Blocksequenzen und viele andere Optimierungen. Da diese Funktion häufig ausgeführt wird, ist sie beim Hotspot JIT normalerweise optimiert und ohne "Aufwärmzeit" einsatzbereit.
quelle
work
Funktion entspricht(doseq [item data] (do-stuff item))
. (Abgesehen von der Tatsache, dass diese Schleife in der Arbeit nie endet.)map
und / oder verallgemeinert werden könnenreduce
.Sammlungstypen haben für einige Vorgänge unterschiedliche Verhaltensweisen:
Das Arbeiten mit Strings kann verwirrend sein (ich verstehe sie immer noch nicht ganz). Insbesondere sind Zeichenfolgen nicht mit Zeichenfolgen identisch, obwohl Sequenzfunktionen auf sie angewendet werden:
Um eine Zeichenfolge wieder herauszuholen, müssen Sie Folgendes tun:
quelle
zu viele Klammern, insbesondere mit dem Aufruf der Java-Methode void, der zu NPE führt:
führt zu NPE aus äußeren Parantheses, da innere Parantheses Null ergeben.
Ergebnisse in der einfacher zu debuggen:
quelle