7 Stimmen

Wie könnten Sie Design-by-Contract in Clojure speziell oder in funktionalen Sprachen im Allgemeinen implementieren?

Ich würde Beispiele in einer Lisp-Variante bevorzugen (Bonuspunkte für Clojure oder Scheme), da ich mich damit am besten auskenne, aber jedes Feedback zu DBC in funktionalen Sprachen wäre für die Gemeinschaft natürlich wertvoll.

Hier ist eine offensichtliche Möglichkeit:

(defn foo [action options]
    (when-not (#{"go-forward" "go-backward" "turn-right" "turn-left"} action)
              (throw (IllegalArgumentException.
                     "unknown action")))
    (when-not (and (:speed options) (> (:speed options) 0))
              (throw (IllegalArgumentException.
                     "invalid speed")))
    ; finally we get to the meat of the logic)

Was mir an dieser Implementierung nicht gefällt, ist, dass die Vertragslogik die Kernfunktionalität verdeckt; der eigentliche Zweck der Funktion geht in bedingten Prüfungen verloren. Dies ist das gleiche Problem, das ich in diese Frage . In einer imperativen Sprache wie Java kann ich Annotationen oder in die Dokumentation eingebettete Metadaten/Attribute verwenden, um den Vertrag aus der Methodenimplementierung herauszulösen.

Hat sich jemand mit dem Hinzufügen von Verträgen zu Metadaten in Clojure beschäftigt? Wie würden Funktionen höherer Ordnung verwendet werden? Welche anderen Möglichkeiten gibt es?

4voto

dnolen Punkte 18276

Clojure hat bereits Unterstützung für Vor- und Nachbedingungen, leider nicht gut dokumentiert:

Sollte ich eine Funktion oder ein Makro verwenden, um Argumente in Clojure zu validieren?

3voto

kotarak Punkte 16848

Ich könnte mir so etwas in Clojure vorstellen:

(defmacro defnc
  [& fntail]
  `(let [logic# (fn ~@(next fntail))]
     (defn ~(first fntail)
       [& args#]
       (let [metadata# (meta (var ~(first fntail)))]
         (doseq [condition# (:preconditions metadata#)]
           (apply condition# args#))
         (let [result# (apply logic# args#)]
           (doseq [condition# (:postconditions metadata#)]
             (apply condition# result# args#))
           result#)))))

(defmacro add-pre-condition!
  [f condition]
  `(do
     (alter-meta! (var ~f) update-in [:preconditions] conj ~condition)
     nil))

(defmacro add-post-condition!
  [f condition]
  `(do
     (alter-meta! (var ~f) update-in [:postconditions] conj ~condition)
     nil))

Eine Beispielsitzung:

user=> (defnc t [a test] (a test))
\#'user/t
user=> (t println "A Test")
A Test
nil
user=> (t 5 "A Test")
java.lang.ClassCastException: java.lang.Integer (NO_SOURCE_FILE:0)
user=> (add-pre-condition! t (fn [a _] (when-not (ifn? a) (throw (Exception. "Aaargh. Not IFn!")))))
nil
user=> (t 5 "A Test")
java.lang.Exception: Aaargh. Not IFn! (NO_SOURCE_FILE:0)
user=> (t println "A Test")
A Test
nil

Sie können also die Funktion definieren und anschließend Vor- und Nachbedingungen festlegen, ohne die Funktionslogik selbst zu überfrachten.

Bedingungsfunktionen sollten eine Ausnahme auslösen, wenn etwas falsch ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X