Question

I'd prefer examples to be in a Lisp variant (bonus points for Clojure or Scheme) since that's what I'm most familiar with, but any feedback regarding DBC in functional lanugages would of course be valuable to the greater community.

Here's an 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 contract logic obscures the core functionality; the true purpose of the function is lost in conditional checks. This is the same issue I raised in this question. In an imperative language like Java, I can use annotations or metadata/attributes embedded in documentation to move the contract out of the method implementation.

Has anyone looked at adding contracts to metadata in Clojure? How would higher-order functions be used? What other options are there?

Was it helpful?

Solution

Clojure already has support for pre and post conditions, unfortunately not well documented:

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

OTHER TIPS

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

An example session:

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 you can define the function and then afterwards define pre- and post-conditions whereever you like, without cluttering the function logic itself.

condition functions should throw an exception if something is wrong.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top