How can you implement design by contract in Clojure specific or functional languages ​​in general?

I would prefer the examples to be in the Lisp variant (bonus points for Clojure or the scheme) since I am most familiar, but any feedback regarding DBC in functional lanugages would certainly be valuable to the larger community.

Here is the obvious way:

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

What I don't like about this implementation is that the logic of the contract hides the core functions; the true purpose of the function is lost in conditional checks. This is the same problem that I raised in this matter . In an imperative language such as Java, I can use annotations or metadata / attributes built into the documentation to transfer the contract from the method implementation.

Has anyone looked at adding metadata contracts to Clojure? How to use higher order functions? What other options are there?

+6
functional-programming clojure scheme design-by-contract
source share
2 answers

Clojure already supports pre and post conditions, unfortunately, are not well documented:

Should I use a function or macro to test arguments in Clojure?

+4
source share

I could imagine something like this in Clojure:

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

Session Example:

 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 

This way you can define a function, and then define the predefined and post-situations that you like, without cluttering up the functional logic itself.

state functions should throw an exception if something is wrong.

+3
source share

All Articles