2011-11-19 13 views
13

In una forma let (Clojure qui) Posso fare qualcosa di similePerché non distruggersi in una forma def?

(let [[u s v] (svd A)] 
    (do-something-with u v)) 

dove svd restituisce una lista di lunghezza tre. Si tratta di una specie molto naturale della cosa da fare, quindi perché non è che noi non abbiamo

(def [u s v] (svd A)) 

e le sue varie generalizzazioni come il comportamento predefinito del modulo def? Non vedo come questo potrebbe interferire con qualsiasi cosa che lo def stia già facendo. Può qualcuno che capisce lo Zen di Lisp o Clojure spiegare perché def non supporta il binding (con destrutturazione) potente come let?

risposta

15

def è un modulo speciale a livello di compilatore: esegue un Var. def deve essere disponibile e utilizzabile prima che la destrutturazione sia disponibile. Si vede qualcosa di simile con let*, una primitiva del compilatore che non supporta la destrutturazione: quindi dopo diverse migliaia di righe in clojure/core.clj il linguaggio è finalmente abbastanza potente da fornire una versione di let con destrutturazione, come una macro sopra let*.

Se lo si desidera, è possibile scrivere una macro (ad esempio, def+) per farlo. Personalmente penso che sia un po 'grossolano e non lo userei, ma usare un Lisp significa usare una lingua che si adatta alle tue esigenze personali.

+1

Penso che questo sia il tipo di risposta a cui ero interessato. A rischio di eccessiva editorializzazione da parte mia, suppongo tu stia dicendo che la ragione per cui questo non viene fatto in Clojure è in parte tecnica (in quanto "def" capita di essere un compilatore primitivo), e in parte per convenzione (in questo caso (ad esempio Rich Hickey) avrebbe potuto iniziare con un 'primitivo' def' * e dichiarato 'def' più tardi in un punto del core). –

+1

@GabrielMitchell sì, sarebbe stato possibile. Ma è molto meno utile per 'def' che per' let', e mancherebbe di simmetria. 'let' ** always ** prende un vettore e destructures all'interno di quello; far sì che 'def' fare lo renderebbe molto meno conveniente, e far sì che la def accettare o un simbolo o una forma destrutturante sia piuttosto terribile IMO. – amalloy

+0

Puoi dire un po 'di più sul perché una tale macro sarebbe grossolana? In questo momento, sto pensando che sia una grande idea, ma, basandomi sul tuo commento, mi chiedo anche se sarebbe in qualche modo in grado di funzionare contro il grano di Clojure. Di solito è meglio andare con la grana della lingua piuttosto che montare qualcosa che va contro di essa ma sembra attraente per qualcuno che non ha esperienza con la lingua. Il fatto che dopo 10 anni, l'uso di una tale macro non sia di uso comune, mi fa pensare se sia una soluzione goffa a un problema risolto in un altro modo. –

2

def è in pratica il costruttore di Vars. Il primo argomento è il simbolo che dà il nome al Var. Prende quel simbolo e restituisce un Var per quel simbolo. La destrutturazione cambierebbe queste semantiche.

Si potrebbe scrivere una macro che lo faccia, però.

+1

Non è quello che hai detto ugualmente vero per "let'? Voglio dire, non si potrebbe fare la stessa argomentazione per dire che "Let' non dovrebbe supportare il legame distruttivo? – sepp2k

+1

Sì, sepp2k evidenzia il punto che mi sto chiedendo. Per quanto ho capito, la differenza fondamentale tra 'def' e' let' è lo scopo dei binding, eppure 'let' sembra avere un comportamento più generale. Come sottolinea Chuck, il fatto che tu possa scrivere una macro che è polimorfa nel primo argomento (un comportamento per un singolo simbolo, un altro comportamento per gli elenchi di simboli) mi fa chiedere perché questa non sia l'implementazione predefinita. –

0

Questo non è perfetto, ma è iniziare a scrivere un def+ https://clojuredocs.org/clojure.core/destructure

(defmacro def+ 
    "binding => binding-form 
    internalizes binding-forms as if by def." 
    {:added "1.9", :special-form true, :forms '[(def+ [bindings*])]} 
    [& bindings] 
    (let [bings (partition 2 (destructure bindings))] 
    (sequence cat 
     ['(do) 
     (map (fn [[var value]] `(def ~var ~value)) bings) 
     [(mapv (fn [[var _]] (str var)) bings)]]))) 

Con che si può fare ...

(def+ [u s v] [1 5 9], foo "bar") 

... senza compromettere la semplicità di def ...

(def+ foo "bar") 

.. .questo è ciò che è stato richiesto e suggerito. Questo ha ancora il problema di introdurre le variabili gensym nello spazio dei nomi globale. Il problema gensym potrebbe essere gestito ma dato il caso d'uso (utilizzare in repl) le variabili aggiuntive sono probabilmente accettabili.

1

Quelle che seguono sono alcune giustificazioni filosofiche.

Clojure favorisce l'immutabilità rispetto alla mutabilità e tutte le fonti di mutabilità devono essere attentamente considerate e denominate. def crea vars mutabili. Pertanto, il Clojure idiomatico non li usa entrambi molto, e inoltre non vorrebbe che fosse troppo facile creare molti vars mutabili senza preoccuparsi (ad esempio mediante destrutturazione). let e la funzione di destrutturazione delle funzioni, tuttavia, crea associazioni immutabili, pertanto Clojure semplifica la creazione di questi binding.

Vars creato da def hanno portata globale. Pertanto dovresti nominare attentamente il numero def vars e tenerli in numero ridotto. Distruzione def renderebbe troppo facile creare molti def s senza problemi. let e la funzione di destrutturazione degli argomenti, d'altra parte, crea associazioni locali, in ambito lessicale, quindi la convenienza della destrutturazione non causa l'inquinamento del nome.