6

Ich würde bevorzugen, dass Beispiele in einer Lisp-Variante (Bonuspunkte für Clojure oder Scheme) sind, da das ist, was ich am vertrautesten bin, aber jede Rückmeldung bezüglich DBC in funktionalen Sprachen wäre natürlich wertvoll für die größere Gemeinschaft.Wie könnten Sie Design-by-Contract in Clojure speziell oder funktionale Sprachen im Allgemeinen implementieren?

Hier ist offensichtlich, dass die Art und Weise:

(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) 

Ich mag an nicht diese Implementierung ist, dass der Vertrag Logik die Kernfunktionalität verschleiert; Der wahre Zweck der Funktion geht bei bedingten Prüfungen verloren. Dies ist das gleiche Problem, das ich in this question angesprochen habe. In einer imperativen Sprache wie Java kann ich Anmerkungen oder Metadaten/Attribute verwenden, die in der Dokumentation eingebettet sind, um den Vertrag aus der Methodenimplementierung zu entfernen.

Hat schon jemand versucht, Verträge mit Metadaten in Clojure hinzuzufügen? Wie würden Funktionen höherer Ordnung verwendet? Welche anderen Möglichkeiten gibt es?

+2

Haben Sie sehen, wie Verträge in PLT-Schema umgesetzt werden? Schau mal. http://docs.plt-scheme.org/guide/contracts.html –

+0

@Alexey - Das ist eine spektakuläre Ressource! Ich bin ziemlich neu in Scheme (arbeite durch The Little/Seasoned Bücher) und ich wusste nicht, dass das existiert, also danke. – rcampbell

+0

Nicht direkt eine Antwort auf Ihre Frage, aber werfen Sie einen Blick auf QuickCheck und seine Derivate (ClojureCheck). Es ist im Wesentlichen eigenschaftsbasiertes Testen, und in Verträgen definieren Sie Eigenschaften, so dass Sie die erzeugten Tests leicht erhalten können. – Masse

Antwort

3

konnte ich so etwas wie dies in Clojure vorstellen:

(defmacro defnc 
    [& fntail] 
    `(let [logic# (fn [email protected](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))

Ein Beispiel-Sitzung:

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

So können Sie die Funktion definieren und dann danach definieren Pre- und Post-Bedingungen wo auch immer Sie mögen , ohne die Funktionslogik selbst zu überladen.

Bedingungsfunktionen sollten eine Ausnahme auslösen, wenn etwas nicht stimmt.