2017-01-10 2 views
1

Ich programmiere etwas, das keine Nebenwirkungen hat, aber mein Code ist nicht gut lesbar. Betrachten Sie das folgende Stück Code:Bedingte "Zuweisung" in der funktionalen Programmierung

(let [csv_data (if header_row (cons header_row data_rows) data_rows)] 
) 

Ich versuche csv_data in einem Block von Code zu verwenden. Was ist eine saubere Art der Konditionierung auf das Vorhandensein eines header_row? Ich habe mir if-let angesehen, konnte aber nicht sehen, wie das hier helfen könnte.

Ich bin in ähnliche Situationen mit funktionalen for-Schleifen geraten, wo ich das Ergebnis an eine lokale Variable binde, und der Code sieht aus wie ein Stapel von Ausdrücken.

Muss ich wirklich in vielen Fällen eine separate Hilfsfunktion erstellen? Was fehlt mir hier?

+0

Meine Antwort war falsch, also habe ich es inzwischen gelöscht. – Thumbnail

+0

Nun, ich denke, es gab nur einen kleinen Fehler, aber es war ansonsten eine vollkommen gültige Antwort. – m33lky

Antwort

2

Verwenden Sie zunächst keinen Unterstrich, sondern Bindestriche.

Zweitens ist nichts falsch mit einer kleinen Hilfsfunktion; Schließlich scheint dies eine Voraussetzung für die Handhabung Ihres speziellen Datenformats zu sein.

Drittens, wenn Sie Ihre Daten ändern können, so dass Sie diese Entscheidungen überspringen können und eine einheitliche Darstellung für alle Ecken Fälle haben, ist dies noch besser. Eine Kopfzeile enthält eine andere Art von Daten, so dass Sie könnte sie getrennt vorziehen zu halten (Spaltennamen?):

(let [csv {:header header :rows rows}] 
    ...) 

Oder vielleicht irgendwann könnten Sie „Header“ und „Reihen“ von der gleichen haben Typ: Reihenfolgen. Dann können Sie sie direkt concattieren. Das ensure-x Idiom ist eine sehr verbreitete Möglichkeit, Ihre Daten zu normalisieren:

(defn ensure-list [data] 
    (and data (list data))) 

Zum Beispiel:

user=> (ensure-list "something") 
("something") 
user=> (ensure-list()) 
(()) 
user=> (ensure-list nil) 
nil 

Und so:

(let [csv (concat (ensure-list header) rows)] 
    ...) 
+0

1. Gibt es irgendwelche historischen Gründe, warum Clojure Striche bevorzugt? 2. Ich mache einen Export von Daten, also muss ich alles zusammenführen und es an eine Lib übergeben. – m33lky

+0

* 2. sollte 3 sein. – m33lky

+1

@ m33lky (1) Strichseparierte Symbole kommen von Lisp; Dies ist nur eine Konvention, wie Sie sie in anderen Sprachen finden können. (2) Vielleicht könnten Sie zu einem Zeitpunkt "Kopfzeilen" und "Zeilen" vom gleichen Typ haben: Reihenfolgen. Dann können Sie es direkt erfassen. Ich stimme zu, dass das Problem woanders hin treibt. – coredump

-1

Nach über die "Kosten" ein Denken Einzeilige Hilfsfunktion im Namensraum Ich habe stattdessen eine lokale Funktion entwickelt:

(let [merge_header_fn (fn [header_row data_rows] 
          (if header_row 
          (cons header_row data_rows) 
          data_rows)) 
     csv_data (merge_header_fn header_row data_rows) ] 
    ... 
    <use csv_data> 
    ... 
) 

Wenn nicht jemand einen eleganteren Umgang damit vorschlagen kann, werde ich dies als Antwort behalten.

+0

Ich sehe hier keine Verbesserung, die Inline-Version ist IMHO besser lesbar. Außerdem, über welche Kosten sprechen Sie? Alte Referenz, aber immer noch relevant: https://groups.google.com/forum/message/raw?msg=comp.lang.lisp/9SKZ5YJUmBg/Fj05OZQomzIJ – coredump

+0

Zu viel geschieht in einer Zeile. Es gibt auch einen Mangel an Struktur. – m33lky

+0

Ich mache oft lokale Hilfsfunktionen wie diese, wenn sie nicht außerhalb des lokalen Kontexts verwendet werden (ich habe die Formatierung etwas modifiziert, um das Lesen zu erleichtern). Ob lokal in einem Let-Ausdruck oder Top-Level in einem Def-Ausdruck, es gibt keine Kosten auf die eine oder andere Weise. Wie immer macht es den Code einfacher, offensichtlicher und korrekter als jede andere Sorge. –

1

Ich würde ein Dienstprogramm Makro vorschlagen. So etwas wie das:

(defmacro update-when [check val-to-update f & params] 
    `(if-let [x# ~check] 
    (~f x# ~val-to-update [email protected]) 
    ~val-to-update)) 

user> (let [header-row :header 
      data-rows [:data1 :data2]] 
     (let [csv-data (update-when header-row data-rows cons)] 
      csv-data)) 
;;=> (:header :data1 :data2) 

user> (let [header-row nil 
      data-rows [:data1 :data2]] 
     (let [csv-data (update-when header-row data-rows cons)] 
      csv-data)) 
;;=> [:data1 :data2] 

es ist ziemlich universell, und Sie können komplexere Aufgaben erfüllen, dann einfach nur Consing. Wie zum Beispiel möchten Sie einige coll umkehren, wenn die Prüfung wahr ist, und eine andere Liste erstellen ...

user> (let [header-row :header 
      data-rows [:data1 :data2]] 
     (let [csv-data (update-when header-row data-rows 
            (fn [h d & params] (apply concat (reverse d) params)) 
            [1 2 3] ['a 'b 'c])] 
      csv-data)) 

;;=> (:data2 :data1 1 2 3 a b c) 

Update wie @amalloy bemerkt haben, dieses Makro eine Funktion sein sollte:

(defn update-when [check val-to-update f & params] 
    (if check 
    (apply f check val-to-update params) 
    val-to-update)) 
6

Verwenden Sie den cond->> Makro

(let [csv_data (cond->> data_rows 
       header_row (cons header-row)] 
    ) 

Es funktioniert wie die regelmäßige ->> Makro, aber vor jedem threading-Formular muss ein Testausdruck platziert werden, der bestimmt, ob der threadin g Formular wird verwendet.

Es gibt auch cond->. Lesen Sie mehr über Threading Makros hier: Official threading macros guide

+0

Während ich sehe, wie das idiomatische fortgeschrittene Clojure ist, finde ich es ein bisschen schwer zu lesen. Die Semantik ist ziemlich geladen: "Beachten Sie, dass einige -> oder cond, cond-> die Auswertung nie kurzschließen, selbst wenn ein Test zu false oder zu null auswertet." Außerdem gab es eine Antwort, die Conditioned> hier gepostet hatte und es gab einen Fehler. Ich schreibe es der kognitiven Belastung zu. Ich verstehe den Wert von Makros, aber hier fühlt es sich schwer für mich an. – m33lky

+1

@ m33lky Kurzschlüsse haben keinen Einfluss auf Ihre Frage. Es bedeutet nur, dass wenn Sie mehrere Test/Form-Paare haben, ein Falsy-Test die anderen Paare nicht beeinflussen wird. Dies ist in keiner Weise ein "schweres" Makro oder fortgeschrittenes Clojure. Es liest sich ganz einfach: Nehmen Sie data_rows, wenn es eine header_row gibt, contra the header_row. Natürlich müssen Sie die Syntax für ein Makro lernen, um es lesen zu können. Betrachten Sie Makros wie case, cond, defn usw. Makros sind Syntax, die gelernt werden müssen. –

+0

Ich wollte sagen, dass es für einen fortgeschrittenen Clojure Programmierer ist. Ich habe persönlich dieses spezielle Makro nie benutzt. Während es leicht ist, diese neue Funktionalität zu erlernen, denke ich, dass es Erfahrung braucht, um in der Lage zu sein, solche Logik mit der gleichen Geschwindigkeit und Sicherheit zu "lesen" wie die eher traditionellen Konstrukte wie if/cond. Stimmen Sie zu, dass ein Cond - >> viel schwerer ist als ein Cond? Es ist ein bisschen ein unfairer Vergleich, da die Anwendungsfälle unterschiedlich sind. – m33lky

Verwandte Themen