2009-08-10 17 views
41

Esto es lo que dijo Rich Hickey en una de las publicaciones del blog, pero no entiendo la motivación para usar apply. Por favor ayuda.¿Por qué debería usar 'aplicar' en Clojure?

Una gran diferencia entre Clojure y CL es que Clojure es un Lisp-1, por lo que no es necesario funcall, y aplicar sólo se utiliza para aplicar una función de tiempo de ejecución de una colección definida por los argumentos. Entonces, (aplicar f [i]) puede escribirse (f i).

Además, ¿qué quiere decir con "Clojure is Lisp-1" y no se necesita funcall? Nunca he programado en CL.

Gracias

+0

Aquí hay una esencia con el código fuente de la función de aplicar: https://gist.github.com/purplejacket/36de7061061ec9c49734 – Purplejacket

Respuesta

51

Se podría utilizar se aplican, si el número de argumentos para pasar a la función no se conoce en tiempo de compilación (lo siento, no sé sintaxis de Clojure del todo bien, recurriendo al esquema):

(define (call-other-1 func arg) (func arg)) 
(define (call-other-2 func arg1 arg2) (func arg1 arg2)) 

Siempre que se conozca la cantidad de argumentos en tiempo de compilación, puede pasarlos directamente como se hace en el ejemplo anterior. Pero si el número de argumentos no se conoce en tiempo de compilación, no se puede hacer esto (bueno, usted podría intentar algo así):

(define (call-other-n func . args) 
    (case (length args) 
     ((0) (other)) 
     ((1) (other (car args))) 
     ((2) (other (car args) (cadr args))) 
     ...)) 

pero que se convierte en una pesadilla lo suficientemente pronto. Ahí es donde aplican entra en escena:

(define (call-other-n func . args) 
    (apply other args)) 

Se necesita cualquier número de argumentos figuran en la lista dada como último argumento a ella, y llama a la función pasada como primer argumento a aplica con esos valores.

2

el patrón habitual para aplicar operaciones de tipo es combinar una función proporcionado en tiempo de ejecución con un conjunto de argumentos, ídem.

No he hecho lo suficiente con clojure para poder confiar en las sutilezas de ese lenguaje en particular para saber si el uso de la aplicación en ese caso sería estrictamente necesario.

38

Los términos Lisp-1 y Lisp-2 se refieren a si las funciones están en el mismo espacio de nombres como variables.

En un Lisp-2 (es decir, 2 espacios de nombres), el primer elemento de un formulario se evaluará como un nombre de función, incluso si en realidad es el nombre de una variable con un valor de función. Entonces, si desea llamar a una función variable, debe pasar la variable a otra función.

En un Lisp-1, como Scheme y Clojure, las variables que evalúan las funciones pueden ir en la posición inicial, por lo que no necesita usar apply para evaluarlo como una función.

28

apply básicamente desenvuelve una secuencia y les aplica la función como argumentos individuales.

Aquí se muestra un ejemplo:

(apply + [1 2 3 4 5]) 

que devuelve 15. Básicamente se expande para (+ 1 2 3 4 5), en lugar de (+ [1 2 3 4 5]).

+7

embargo, otro ejemplo: (aplicar + 1 2 [3 4 5]) => 15 – Isaiah

+0

... pero esto falla (aplicar + 1 2 [3 4 5]) que fue inesperado. ¿Alguna idea de por qué? (Soy completamente nuevo en Clojure) – ianjs

+0

@ianjs que no falla ... – justin

0

Aplicar es útil con los protocolos, especialmente en combinación con las macros de subprocesamiento. Acabo de descubrir esto. Desde can't use the & macro to expand interface arguments at compile time, puede aplicar un vector de tamaño impredecible en su lugar.

Así que lo uso, por ejemplo, como parte de una interfaz entre un registro que contiene algunos metadatos sobre un archivo xml particular y el archivo en sí.

(query-tree [this forms] 
    (apply xml-> (text-id-to-tree this) forms))) 

text-id-to-tree es otro método de este registro particular que analiza un archivo XML en una cremallera. En otro archivo, extiendo el protocolo con una consulta en particular que implementa query-tree, especificando una cadena de comandos para ser roscado a través de la xml-> macro:

(tags-with-attrs [this] 
    (query-tree this [zf/descendants zip/node (fn [node] [(map #(% node) [:tag :attrs])])]) 

(nota: esta consulta por sí mismo devolverá un montón de Resultados "nil" para etiquetas que no tienen los atributos . Filtre y reduzca para obtener una lista limpia de valores únicos).

zf, por cierto, se refiere a clojure.contrib.zip-filter, y zip a clojure.zip. La macro xml-> es de la biblioteca clojure.contrib.zip-filter.xml, que I :use

6

Utiliza apply para convertir una función que funciona en varios argumentos en uno que funciona en una única secuencia de argumentos. También puede insertar argumentos antes de la secuencia. Por ejemplo, map puede trabajar en varias secuencias. Este ejemplo (de ClojureDocs) usa map para transponer una matriz.

user=> (apply map vector [[:a :b] [:c :d]]) 
([:a :c] [:b :d]) 

El argumento insertado aquí es vector. Por lo que el apply expande a

user=> (map vector [:a :b] [:c :d]) 

lindo!

PS Para devolver un vector de vectores en lugar de una secuencia de vectores, envuelva toda la cosa en vec:

user=> (vec (apply map vector [[:a :b] [:c :d]])) 

Ya que estamos aquí, vec se podría definir como (partial apply vector), aunque no es .

Relativo a Lisp-1 y Lisp-2: el 1 y 2 indican el número de cosas que un nombre puede denotar en un contexto dado. En un Lisp-2, puede tener dos cosas diferentes (una función y una variable) con el mismo nombre. Entonces, donde sea que sea válido, necesitas decorar tu programa con algo que indique a qué te refieres. Afortunadamente, Clojure (o Scheme ...) permite que un nombre denote solo una cosa, por lo que no se necesitan tales decoraciones.

Cuestiones relacionadas