2012-03-27 13 views
11

Todos los ejemplos están tomados del libro SICP: http://sicpinclojure.com/?q=sicp/1-3-3-procedures-general-methods¿Cuál es la forma estándar de escribir declaraciones de definición anidadas (como en el esquema) para clojure?

Esto fue motivado por la serie de videos del MIT en LISP - http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-001-structure-and-interpretation-of-computer-programs-spring-2005/video-lectures/2a-higher-order-procedures/

En el esquema, se puede poner 'definir' dentro de otro 'definir':

(define (close-enough? v1 v2) 
    (define tolerance 0.00001) 
    (< (abs (- v1 v2)) tolerance)) 

En clojure, no es el 'vamos' declaración con la única diferencia de que se anida:

(defn close-enough? [v1 v2] 
    (let [tolerance 0.00001] 
     (< (Math/abs (- v1 v2)) 
      tolerance))) 

Pero ¿qué pasa con la reescritura en clojure algo más grande como esto ?:

(define (sqrt x) 
    (define (fixed-point f first-guess) 
    (define (close-enough? v1 v2) 
     (define tolerance 0.00001) 
     (< (abs (- v1 v2)) tolerance)) 
    (define (try guess) 
     (let ((next (f guess))) 
     (if (close-enough? guess next) 
      next 
      (try next)))) 
    (try first-guess)) 
    (fixed-point (lambda (y) (average y (/ x y))) 
      1.0)) 

Este hecho funciona, pero se ve muy poco convencional ...

(defn sqrt [n] 
    (let [precision  10e-6 
     abs    #(if (< % 0) (- %) %) 
     close-enough? #(-> (- %1 %2) abs (< precision)) 
     averaged-func #(/ (+ (/ n %) %) 2) 
     fixed-point  (fn [f start] 
          (loop [old start 
            new (f start)] 
           (if (close-enough? old new) 
            new 
            (recur new (f new)))))] 

     (fixed-point averaged-func 1))) 

(sqrt 10) 

ACTUALIZADO Mar/8/2012

¡Gracias por la respuesta!

Esencialmente 'letfn' no es muy diferente de 'let' - las funciones que se invocan deben anidarse en la definición 'letfn' (en oposición al Scheme donde las funciones se usan en el siguiente sexp después de sus definiciones y solo existente dentro del alcance de la función de nivel superior en la que está definido).

Otra pregunta ... ¿Por qué no proporciona Clojure la capacidad de hacer lo que hace el esquema? ¿Es algún tipo de decisión de diseño de lenguaje? Lo que me gusta de la organización esquema es:

  • 1) La encapsulación de las ideas por lo que yo, como el programador tenga una idea de bloque grande lo que se están utilizando pequeños bloques - especialmente si sólo estoy usando la pequeña bloquea una vez dentro del bloque grande (por la razón que sea, incluso si los pequeños bloques son útiles por derecho propio).

  • 2) Esto también deja de contaminar el espacio de nombres con pequeños procedimientos que no son útiles para el usuario final (escribí programas clojure, volví a ellos una semana después y tuve que volver a aprender mi código porque era en una estructura plana y sentí que estaba mirando el código de adentro hacia afuera en lugar de hacerlo de arriba hacia abajo).

  • 3) Una interfaz de definición de método común para que pueda extraer un submétodo en particular, eliminar el error y probarlo, y pegar la versión modificada de nuevo sin demasiado alboroto.

¿Por qué no está implementado en clojure?

+1

es una pregunta interesante, pero no creo que intente poner todo eso en una función. – Kevin

Respuesta

15

la forma estándar de escribir procedimientos anidados, nombrados en clojure es usar letfn.

como un lado, su ejemplo de uso de funciones anidadas es bastante sospechoso. todas las funciones en el ejemplo podrían ser funciones no locales de nivel superior, ya que son más o menos útiles por sí mismas y no se cierran sobre nada sino entre sí.

+0

¡Gracias por eso! Por favor, eche un vistazo a la pregunta actualizada! – zcaudate

11

Las personas que critican su colocación de esto en todas las funciones, no entienden el contexto de por qué se ha hecho de esa manera.SICP, de donde viene el ejemplo, intentaba ilustrar el concepto de un módulo, pero sin agregar ningún otro constructo al lenguaje base. Entonces "sqrt" es un módulo, con una función en su interfaz, y el resto son funciones locales o privadas dentro de ese módulo. Esto se basó en el esquema R5RS, creo, y los esquemas posteriores han agregado una construcción de módulo estándar, creo (?). Pero independientemente, es más una demostración del principio de ocultar la implementación.

El programador experimentado también realiza ejemplos similares de funciones locales anidadas, pero generalmente tanto para ocultar la implementación como para cerrar los valores.

Pero incluso si esto no fuera un ejemplo pedagógico, puede ver cómo este es un módulo muy ligero y probablemente lo escriba de esta manera dentro de un módulo "real" más grande. La reutilización está bien, si está previsto. De lo contrario, solo expondrá las funciones que probablemente no se adaptarán perfectamente a lo que necesita más adelante y, al mismo tiempo, sobrecargará esas funciones con casos de uso inesperados que podrían romperlas más adelante.

4

letfn es la manera estándar.

Pero como Clojure es un Lisp, puede crear (casi) cualquier semántica que desee. Aquí hay una prueba de concepto que define define en términos de letfn.

(defmacro define [& form] 
    (letfn [(define? [exp] 
      (and (list? exp) (= (first exp) 'define))) 
      (transform-define [[_ name args & exps]] 
      `(~name ~args 
       (letfn [[email protected](map transform-define (filter define? exps))] 
       [email protected](filter #(not (define? %)) exps))))] 
    `(defn [email protected](transform-define `(define [email protected]))))) 

(define sqrt [x] 
    (define average [a b] (/ (+ a b) 2)) 
    (define fixed-point [f first-guess] 
    (define close-enough? [v1 v2] 
     (let [tolerance 0.00001] 
     (< (Math/abs (- v1 v2)) tolerance))) 
    (define tryy [guess] 
     (let [next (f guess)] 
     (if (close-enough? guess next) 
      next 
      (tryy next)))) 
    (tryy first-guess)) 
    (fixed-point (fn [y] (average y (/ x y))) 
       1.0)) 

(sqrt 10) ; => 3.162277660168379 

Para código real, que te gustaría cambiar define a comportarse más como R5RS: permitir que los valores no-fn, estará disponible en defn, defmacro, let, letfn, y fn, y verifique que las definiciones internas están al comienzo del cuerpo circundante.

Nota: Tuve que cambiar el nombre try al tryy. Aparentemente, try es una construcción especial sin función y sin macro cuya redefinición falla silenciosamente.

+0

Eso es muy genial. ¡Ojalá pueda dar tu respuesta más votaciones! – zcaudate

Cuestiones relacionadas