2009-08-10 16 views
41

Questo è ciò che Rich Hickey ha detto in uno dei post del blog ma non capisco la motivazione nell'utilizzo di apply. Per favore aiuto.Perché dovrei usare 'apply' in Clojure?

Una grande differenza tra Clojure e CL è che Clojure è un Lisp-1, quindi funcall non è necessario, e applicare viene utilizzato solo per applicare una funzione di un insieme definito di runtime-argomenti. Quindi, (applicare f [i]) può essere scritto (f i).

Inoltre, cosa significa "Clojure is Lisp-1" e funcall non è necessario? Non ho mai programmato in CL.

Grazie

+0

Ecco una sostanza con il codice sorgente della funzione applica: https://gist.github.com/purplejacket/36de7061061ec9c49734 – Purplejacket

risposta

51

Si potrebbe utilizzare applicano, se il numero di argomenti da passare alla funzione non è noto al momento della compilazione (mi dispiace, non so sintassi Clojure tanto bene, ricorrendo a Scheme):

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

Fintanto che il numero di argomenti è noto al momento della compilazione, è possibile passarli direttamente come nell'esempio sopra riportato. Ma se il numero di argomenti non è noto al momento della compilazione, non si può fare questo (beh, si potrebbe provare qualcosa di simile):

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

ma che diventa un incubo abbastanza presto. Ecco dove applicano entra in scena:

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

Prende qualunque sia il numero di argomenti sono contenuti nella lista data come ultimo argomento ad esso, e chiama la funzione passata come primo argomento a applicare con quei valori.

2

Il modello usuale per applicare operazioni di tipo è quella di combinare una funzione fornita in fase di esecuzione di una serie di argomenti, idem.

Non ho fatto abbastanza con il clojure per poter essere sicuro delle sottigliezze di quel particolare linguaggio per dire se l'uso di applicare in quel caso sarebbe strettamente necessario.

38

I termini Lisp-1 e Lisp-2 si riferiscono a se le funzioni si trovano nello stesso spazio dei nomi delle variabili.

In un Lisp-2 (ovvero 2 spazi dei nomi), il primo elemento di un modulo verrà valutato come un nome di funzione, anche se in realtà è il nome di una variabile con un valore di funzione. Quindi, se si desidera chiamare una funzione variabile, è necessario passare la variabile a un'altra funzione.

In un Lisp-1, come Schema e Clojure, le variabili che valutano le funzioni possono andare nella posizione iniziale, quindi non è necessario utilizzare apply per poterle valutare come una funzione.

28

apply interrompe sostanzialmente una sequenza e applica la funzione a loro come argomenti individuali.

Ecco un esempio:

(apply + [1 2 3 4 5]) 

che restituisce 15. Si espande fondamentalmente (+ 1 2 3 4 5), invece di (+ [1 2 3 4 5]).

+7

Ancora un altro esempio: (a + 1 2 [3 4 5]) => 15 – Isaiah

+0

... ma questo fallisce (applica + 1 2 [3 4 5]) che era inaspettato. Qualche idea del perché? (Sono completamente nuovo a Clojure) – ianjs

+0

@ianjs che non fallisce ... – justin

0

L'applicazione è utile con i protocolli, specialmente in combinazione con macro di threading. L'ho appena scoperto. Dal momento che è can't use the & macro to expand interface arguments at compile time, è possibile invece applicare un vettore di dimensioni imprevedibili.

Quindi lo uso, ad esempio, come parte di un'interfaccia tra un record contenente alcuni metadati su un particolare file xml e il file stesso.

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

text-id-to-tree è un altro metodo di questo particolare record che analizza un file in una cerniera XML. In un altro file, estendo il protocollo con una query che implementa query-tree, specificando una serie di comandi da filettare attraverso il XML> macro:

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

(nota: questa ricerca per sé restituirà un sacco di risultati "nulli" per tag che non hanno attributi . Filtro e riduzione per un elenco pulito di valori univoci).

zf, a proposito, fa riferimento a clojure.contrib.zip-filter e zip a clojure.zip. L'XML> macro è dalla libreria clojure.contrib.zip-filter.xml, che ho :use

6

si utilizza apply per convertire una funzione che lavora su vari argomenti per uno che funziona su una singola sequenza di argomenti. È inoltre possibile inserire argomenti prima della sequenza. Ad esempio, map può funzionare su più sequenze. Questo esempio (da ClojureDocs) utilizza map per trasporre una matrice.

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

L'argomento inserito qui è vector. Quindi lo apply si espande a

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

Carino!

PS Per restituire un vettore di vettori al posto di una sequenza di vettori, avvolgere il tutto in vec:

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

Mentre siamo qui, vec potrebbe essere definito come (partial apply vector), anche se non è .

Riguardo a Lisp-1 e Lisp-2: i numeri 1 e 2 indicano il numero di cose che un nome può denotare in un dato contesto. In un Lisp-2, puoi avere due cose diverse (una funzione e una variabile) con lo stesso nome. Quindi, ovunque sia valido, è necessario decorare il programma con qualcosa per indicare ciò che intendi. Per fortuna, Clojure (o Scheme ...) consente a un nome di denotare solo una cosa, quindi non sono necessarie tali decorazioni.