Announcement

Collapse
No announcement yet.

Anmerkungen zum Artikel "Clojure unter der Lupe" in Heft 2/2011

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Anmerkungen zum Artikel "Clojure unter der Lupe" in Heft 2/2011

    In dem Artikel werden zwar Atoms und Agents eingeführt, es fehlt aber die Erwähnung von Refs (synchrone und koordinierte Änderungen) komplett.

    Die papier-schere-stein Funktion ist durch die verschachtelte Verwendung des Cond Makros unnötig kompliziert. Gerade hier würde sich die Verwendung einer Clojure Map als Prädikatsfunktion (data as code) anbieten:

    Code:
    (defn papier-schere-stein [a b]
      (let [schlaegt {:stein :schere, :schere :papier :papier :stein}]
        (cond
         (= (schlaegt a) b) a
         (= (schlaegt b) a) b
         :else :unentschieden)))
    In der Funktion fsm-run wäre die Verwendung von Vektoren als geeignete Datenstruktur angebracht gewesen. Gerade die Sequence Abstraction ist einer der großen Vorteile von Clojure gegenüber traditionellen Lisps. conj ermöglicht hier das "Anhängen" von Werten in kostanter Zeit und macht das abschließende/ineffiziente reverse unnötig:

    Code:
    (defn fsm-run [state & events]
      (loop [history [state] events events]
        (if-let [e (first events)]
          (recur (conj history ((fsm (last history) {}) e :ILLEGAL)) (rest events))
          history)))
    Auch schiebepuzzle entspricht nicht idiomatischen Clojure Code. Zum Ausführen von Code mit Seiteneffekten in Sequence-Bindings existiert das doseq Macro. Das Beispiel lässt insgesamt auf eine lange imperative Programmiertradition schließen . Die "Low-Level" Rekursion/Iteration über den Index lässt sich durch die Verwendung einer Map vermeiden und der Code erscheint um einiges kompakter/lesbarer:


    Code:
    (defn schiebepuzzle []
      (let [frei (atom 0)
            frame (JFrame. "Schiebung")
            fpanel (JPanel. (GridLayout. laenge laenge))
            buttons (into {} (for [idx (range anzahl) ] [idx (JButton. (if (pos? idx) (str idx) ""))]))]
        (doseq [[i btn] buttons]
          (.add fpanel btn)
          (.addActionListener btn (action-listener i frei buttons)))
        (.add frame fpanel)
        (.pack frame)
        (.setVisible frame true)))
    Bei der Konstruktion des Action-Listeners erscheint die Verwendung von proxy unangebracht.
    Mit der Einführung von Protocols in Clojure 1.2 sollte (wenn möglich) auf die Verwendung von proxy zugunsten der Performance und Typesicherheit von reify verzichtet werden.
    Auch die Verwendung von swap! zum Setzen eines Atoms auf einen neuen Wert (unabhängig vom alten) ist nicht idiomatisch. Hierfür existiert die Funktion reset!:

    Code:
    (defn action-listener [idx frei buttons]
      (reify java.awt.event.ActionListener
        (actionPerformed [_ e]
                         (if (beweglich? idx @frei)
                           (let [source (.getSource e)]
                             (.setText (buttons @frei) (.getText source))
                             (.setText source "")
                             (reset! frei idx))))))
    Auch die Funktion beweglich? wird durch die verschachtelten and/or Makros unübersichtlich und wenig idiomatisch. Auch hier bietet sich die Verwendung von Sets als Prädikatsfunktionen an:

    Code:
    (defn beweglich? [idx frei]
      (let [[idx-zeile idx-spalte] (zeile-spalte idx)
            [frei-zeile frei-spalte] (zeile-spalte frei)]
        (cond
         (= idx-zeile frei-zeile)   (#{(dec idx-spalte) (inc idx-spalte)} frei-spalte)
         (= idx-spalte frei-spalte) (#{(inc idx-zeile) (dec idx-zeile)} frei-zeile))))
Working...
X