Как вы могли бы реализовать дизайн по контракту в Clojure в частности или в функциональных языках в целом?

Я бы предпочел, чтобы примеры были в варианте 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)

Что мне не нравится в этой реализации, так это то, что логика контракта скрывает основные функции; истинное назначение функции теряется при условных проверках. Это та же проблема, которую я поднял в этом вопросе. В императивном языке, таком как Java, я могу использовать аннотации или метаданные / атрибуты, встроенные в документацию, чтобы вывести контракт из реализации метода.

Кто-нибудь смотрел на добавление контрактов к метаданным в Clojure? Как можно использовать функции высшего порядка? Какие еще есть варианты?


person Community    schedule 08.12.2009    source источник
comment
Вы смотрели, как в plt-схеме реализуются контракты? Посмотри. docs.plt-scheme.org/guide/contracts.html   -  person Alexey Voinov    schedule 08.12.2009
comment
@Alexey - Замечательный ресурс! Я новичок в Scheme (работаю с книгами The Little / Seasoned), и я не знал, что это существует, так что спасибо.   -  person Robert Campbell    schedule 08.12.2009
comment
Не прямо ответ на ваш вопрос, но взгляните на QuickCheck и его производные (ClojureCheck). В основном это тестирование на основе свойств, и в контрактах вы определяете свойства, чтобы вы могли легко получить и сгенерированные тесты.   -  person Masse    schedule 05.08.2011


Ответы (2)


Clojure уже поддерживает предварительные и пост-условия, к сожалению, плохо документированные:

Должен ли я использовать функцию или макрос для проверки аргументов в Clojure?

person Community    schedule 09.12.2009
comment
Условия до и после публикации станут официальной частью Clojure 1.1 и будут выпущены в любой момент. - person Stuart Sierra; 17.12.2009

Я мог вообразить что-то подобное в 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

Таким образом, вы можете определить функцию, а затем определить предварительные и последующие условия, где захотите, не загромождая саму логику функции.

условные функции должны вызывать исключение, если что-то не так.

person Community    schedule 08.12.2009