6

Я бы предпочел, чтобы примеры были в варианте Лиспа (бонусные баллы для Clojure или Scheme), так как это то, что я больше всего знаю, но любые отзывы о DBC в функциональных lanugages, конечно, были бы ценны для большего сообщества.Как вы можете реализовать дизайн по контракту на Clojure специально или функциональных языках в целом?

Вот очевидный способ:

(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

Вы смотрели на то, как контракты реализуются в PLT-схеме? Взглянуть. http://docs.plt-scheme.org/guide/contracts.html –

+0

@Alexey - Это захватывающий ресурс! Я довольно новичок в Scheme (работая через The Little/Seasoned books), и я не знал, что это существовало, так что спасибо. – rcampbell

+0

Непосредственно ответ на ваш вопрос, но посмотрите QuickCheck и его производные (ClojureCheck). Это, в основном, тестирование на основе свойств, а в контрактах вы определяете свойства, чтобы вы могли легко сгенерировать тесты. – Masse

ответ

4

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

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

+1

Предпосылки/Должности будут официальной частью Clojure 1.1, которые должны быть выпущены в любой день. –

3

я мог себе представить что-то подобное в Clojure:

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

Пример сеанса:

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^