Vielen Dank für all die schönen Antworten! Kann nicht nur einen als korrekt markieren
Hinweis: Bereits ein Wiki
Ich bin neu in der funktionalen Programmierung und obwohl ich einfache Funktionen in der funktionalen Programmierung lesen kann, z. B. um die Fakultät einer Zahl zu berechnen, fällt es mir schwer, große Funktionen zu lesen. Ein Grund dafür ist, dass ich nicht in der Lage bin, die kleineren Codeblöcke innerhalb einer Funktionsdefinition herauszufinden, und zum Teil, weil es für mich immer schwieriger wird, ( )
im Code übereinzustimmen.
Es wäre großartig, wenn mich jemand durch das Lesen eines Codes führen und mir einige Tipps geben könnte, wie man einen Code schnell entschlüsselt.
Hinweis: Ich kann diesen Code verstehen, wenn ich ihn 10 Minuten lang anstarre, aber ich bezweifle, dass derselbe Code, der in Java geschrieben wurde, 10 Minuten dauern würde. Ich denke, um mich im Lisp-Code wohl zu fühlen, muss ich es schneller machen
Hinweis: Ich weiß, dass dies eine subjektive Frage ist. Und ich suche hier keine nachweislich richtige Antwort. Nur Kommentare dazu, wie Sie diesen Code lesen, wären willkommen und sehr hilfreich
(defn concat
([] (lazy-seq nil))
([x] (lazy-seq x))
([x y]
(lazy-seq
(let [s (seq x)]
(if s
(if (chunked-seq? s)
(chunk-cons (chunk-first s) (concat (chunk-rest s) y))
(cons (first s) (concat (rest s) y)))
y))))
([x y & zs]
(let [cat (fn cat [xys zs]
(lazy-seq
(let [xys (seq xys)]
(if xys
(if (chunked-seq? xys)
(chunk-cons (chunk-first xys)
(cat (chunk-rest xys) zs))
(cons (first xys) (cat (rest xys) zs)))
(when zs
(cat (first zs) (next zs)))))))]
(cat (concat x y) zs))))
Antworten:
Insbesondere Lisp-Code ist aufgrund der regulären Syntax noch schwerer zu lesen als andere funktionale Sprachen. Wojciech gibt eine gute Antwort, um Ihr semantisches Verständnis zu verbessern. Hier finden Sie eine Hilfe zur Syntax.
Machen Sie sich beim Lesen von Code keine Gedanken über Klammern. Sorgen Sie sich um Einrückungen. Die allgemeine Regel lautet, dass Dinge auf derselben Einrückungsstufe miteinander verbunden sind. Damit:
(if (chunked-seq? s) (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) (cons (first s) (concat (rest s) y)))
Zweitens, wenn Sie nicht alles in eine Zeile einfügen können, rücken Sie die nächste Zeile ein wenig ein. Dies sind fast immer zwei Leerzeichen:
(defn concat ([] (lazy-seq nil)) ; these two fit ([x] (lazy-seq x)) ; so no wrapping ([x y] ; but here (lazy-seq ; (lazy-seq indents two spaces (let [s (seq x)] ; as does (let [s (seq x)]
Drittens, wenn mehrere Argumente für eine Funktion nicht in eine einzelne Zeile passen, richten Sie das zweite, dritte usw. Argument unter der Anfangsklammer des ersten aus. Viele Makros haben eine ähnliche Regel mit Variationen, damit die wichtigen Teile zuerst angezeigt werden.
; fits on one line (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) ; has to wrap: line up (cat ...) underneath first ( of (chunk-first xys) (chunk-cons (chunk-first xys) (cat (chunk-rest xys) zs)) ; if you write a C-for macro, put the first three arguments on one line ; then the rest indented two spaces (c-for (i 0) (< i 100) (add1 i) (side-effects!) (side-effects!) (get-your (side-effects!) here))
Diese Regeln helfen Ihnen, Blöcke im Code zu finden: wenn Sie sehen
(chunk-cons (chunk-first s)
Zählen Sie keine Klammern! Überprüfen Sie die nächste Zeile:
(chunk-cons (chunk-first s) (concat (chunk-rest s) y))
Sie wissen, dass die erste Zeile kein vollständiger Ausdruck ist, da die nächste Zeile darunter eingerückt ist.
Wenn Sie das
defn concat
von oben sehen, wissen Sie, dass Sie drei Blöcke haben, weil es drei Dinge auf derselben Ebene gibt. Aber alles unterhalb der dritten Zeile wird darunter eingerückt, sodass der Rest zu diesem dritten Block gehört.Hier ist ein Styleguide für Scheme . Ich kenne Clojure nicht, aber die meisten Regeln sollten gleich sein, da keiner der anderen Lisps sehr unterschiedlich ist.
quelle
Ich denke, es
concat
ist ein schlechtes Beispiel, um zu versuchen, es zu verstehen. Es ist eine Kernfunktion und niedriger als Code, den Sie normalerweise selbst schreiben würden, da es effizient sein soll.Beachten Sie auch, dass Clojure-Code im Vergleich zu Java-Code extrem dicht ist. Ein kleiner Clojure-Code macht viel Arbeit. Der gleiche Code in Java würde nicht 23 Zeilen sein. Es wären wahrscheinlich mehrere Klassen und Schnittstellen, sehr viele Methoden, viele lokale temporäre Wegwerfvariablen und umständliche Schleifenkonstrukte und im Allgemeinen alle Arten von Boilerplates.
Einige allgemeine Tipps ...
Versuchen Sie, die Eltern die meiste Zeit zu ignorieren. Verwenden Sie stattdessen die Einrückung (wie Nathan Sanders vorschlägt). z.B
(if s (if (chunked-seq? s) (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) (cons (first s) (concat (rest s) y))) y))))
Wenn ich mir das ansehe, sieht mein Gehirn:
if foo then if bar then baz else quux else blarf
Wenn Sie den Cursor auf einen Paren setzen und Ihr Texteditor den passenden nicht syntaktisch hervorhebt, empfehle ich Ihnen, einen neuen Editor zu finden.
Manchmal hilft es, Code von innen nach außen zu lesen. Clojure-Code ist in der Regel tief verschachtelt.
(let [xs (range 10)] (reverse (map #(/ % 17) (filter (complement even?) xs))))
Schlecht: "Also fangen wir mit Zahlen von 1 bis 10 an. Dann kehren wir die Reihenfolge der Zuordnung der Filterung des Komplements der Wartezeit um. Ich habe vergessen, wovon ich spreche."
Gut: "OK, also nehmen wir etwas
xs
.(complement even?)
Bedeutet das Gegenteil von gerade, also" ungerade ". Wir filtern also eine Sammlung, sodass nur noch die ungeraden Zahlen übrig bleiben. Dann teilen wir sie alle durch 17. Dann wir Ich kehre die Reihenfolge um. Und diexs
fraglichen sind 1 bis 10, gotcha. "Manchmal hilft es, dies explizit zu tun. Nehmen Sie die Zwischenergebnisse, werfen Sie sie in ein
let
und geben Sie ihnen einen Namen, damit Sie verstehen. Die REPL ist dafür gemacht, so herumzuspielen. Führen Sie die Zwischenergebnisse aus und sehen Sie, was Ihnen jeder Schritt bringt.(let [xs (range 10) odd? (complement even?) odd-xs (filter odd? xs) odd-xs-over-17 (map #(/ % 17) odd-xs) reversed-xs (reverse odd-xs-over-17)] reversed-xs)
Bald werden Sie in der Lage sein, solche Dinge mental ohne Anstrengung zu tun.
Machen Sie liberalen Gebrauch von
(doc)
. Der Nutzen der Verfügbarkeit von Dokumentation direkt bei der REPL kann nicht genug betont werden. Wenn Sieclojure.contrib.repl-utils
Ihre .clj-Dateien verwenden und im Klassenpfad haben, können Sie den(source some-function)
gesamten Quellcode dafür anzeigen . Sie können(show some-java-class)
eine Beschreibung aller darin enthaltenen Methoden anzeigen. Und so weiter.Etwas schnell lesen zu können, bringt nur Erfahrung mit sich. Lisp ist nicht schwerer zu lesen als jede andere Sprache. Es kommt einfach so vor, dass die meisten Sprachen wie C aussehen und die meisten Programmierer die meiste Zeit damit verbringen, dies zu lesen. Es scheint also, dass die C-Syntax einfacher zu lesen ist. Üben üben üben.
quelle
Denken Sie zunächst daran, dass das Funktionsprogramm aus Ausdrücken und nicht aus Anweisungen besteht. Zum Beispiel nimmt form
(if condition expr1 expr2)
sein erstes Argument als Bedingung, um den booleschen Fehler zu testen, wertet ihn aus, und wenn er auf true ausgewertet wird, wertet er expr1 aus und gibt ihn zurück, andernfalls wertet er expr2 aus und gibt ihn zurück. Wenn jedes Formular einen Ausdruck zurückgibt, verschwinden möglicherweise einige der üblichen Syntaxkonstrukte wie THEN- oder ELSE-Schlüsselwörter. Beachten Sie, dass hierif
selbst auch ein Ausdruck ausgewertet wird.Nun zur Auswertung: In Clojure (und anderen Lisps) sind die meisten Formulare Funktionsaufrufe des Formulars
(f a1 a2 ...)
, bei denen alle Argumentef
vor dem eigentlichen Funktionsaufruf ausgewertet werden. Formulare können aber auch Makros oder spezielle Formulare sein, die einige (oder alle) ihrer Argumente nicht bewerten. Im Zweifelsfall konsultieren Sie die Dokumentation(doc f)
oder checken Sie einfach REPL ein:user=> apply
eine Funktion ein Makro.#<core$apply__3243 clojure.core$apply__3243@19bb5c09>
user=> doseq
java.lang.Exception: Can't take value of a macro: #'clojure.core/doseq
Diese beiden Regeln:
sollte das Durchsuchen von Lisp-Programmen erleichtern, insb. wenn sie schöne Einrückungen haben, wie das Beispiel, das Sie gegeben haben.
Hoffe das hilft.
quelle