您可以如何在Clojure特定语言或函数语言中实现合同设计?

时间:2009-12-08 07:39:15

标签: functional-programming clojure scheme design-by-contract

我更喜欢使用Lisp变体(Clojure或Scheme的奖励积分)的例子,因为这是我最熟悉的,但任何关于功能性语言中DBC的反馈当然对更大的社区都有价值。

这是一个显而易见的方式:

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

我对此实现不喜欢的是合同逻辑掩盖了核心功能;在条件检查中,函数的真正目的会丢失。这与我在this question中提出的问题相同。在像Java这样的命令式语言中,我可以使用文档中嵌入的注释或元数据/属性来将合同移出方法实现。

有没有人考虑过在Clojure中向元数据添加合同?如何使用高阶函数?还有哪些其他选择?

2 个答案:

答案 0 :(得分:4)

Clojure已经支持前后条件,遗憾的是没有详细记录:

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

答案 1 :(得分:3)

我可以在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))

示例会话:

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

因此,您可以定义函数,然后根据需要定义前置和后置条件,而不会使函数逻辑本身混乱。

如果出现问题,

条件函数应抛出异常。