6

Prefiero que los ejemplos estén en una variante de Lisp (puntos de bonificación para Clojure o Scheme) ya que eso es con lo que estoy más familiarizado, pero cualquier comentario sobre DBC en lanzas funcionales sería por supuesto valioso para la comunidad en general.¿Cómo podría implementar el diseño por contrato en Clojure específicamente o en los lenguajes funcionales en general?

Aquí es una forma obvia:

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

Lo que no me gusta de esta aplicación es que la lógica de contrato oscurece la funcionalidad del núcleo; el verdadero propósito de la función se pierde en las verificaciones condicionales. Este es el mismo problema que planteé en this question. En un lenguaje imperativo como Java, puedo usar anotaciones o metadatos/atributos integrados en la documentación para sacar el contrato de la implementación del método.

¿Alguien ha tratado de agregar contratos a los metadatos en Clojure? ¿Cómo se usarían las funciones de orden superior? ¿Qué otras opciones hay?

+2

¿Ha visto cómo se implementan en los contratos PLT-esquema? Echar un vistazo. http://docs.plt-scheme.org/guide/contracts.html –

+0

@Alexey - ¡Es un recurso espectacular! Soy bastante nuevo para Scheme (trabajando a través de los libros The Little/Seasoned) y no sabía que esto existía, así que gracias. – rcampbell

+0

No es una respuesta directa a su pregunta, pero eche un vistazo a QuickCheck y sus derivados (ClojureCheck). Básicamente se trata de pruebas basadas en propiedades, y en los contratos se definen propiedades para que pueda obtener fácilmente las pruebas generadas también. – Masse

Respuesta

3

que podía imaginar algo como esto en 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))

una sesión de ejemplo:

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

Así se puede definir la función y luego después definir pre y post-condiciones dondequiera que te gusta , sin abarrotar la lógica de la función en sí misma.

las funciones de condición deberían arrojar una excepción si algo está mal.

Cuestiones relacionadas