2010-11-03 12 views
13

SQL offre una funzione denominata coalesce(a, b, c, ...) che restituisce null se tutti i suoi argomenti sono nulli, altrimenti restituisce il primo argomento non null.Clojure coalesce function

Come andresti a scrivere qualcosa del genere in Clojure?

Si chiamerà così: (coalesce f1 f2 f3 ...) dove il fi sono forme che dovrebbero essere valutati solo se necessario. Se f1 non è zero, non è necessario valutare f2 - potrebbe avere effetti collaterali.

Forse Clojure offre già una tale funzione (o macro).

EDIT: Ecco una soluzione che mi è venuta (modificata dalla programmazione di Stuart Halloway Clojure, (and ...) macro a pagina 206):

(defmacro coalesce 
    ([] nil) 
    ([x] x) 
    ([x & rest] `(let [c# ~x] (if c# c# (coalesce [email protected]))))) 

sembra funzionare.

(defmacro coalesce 
    ([] nil) 
    ([x] x) 
    ([x & rest] `(let [c# ~x] (if (not (nil? c#)) c# (coalesce [email protected]))))) 

Risolto.

risposta

5

in base alla risposta del nickik e "o" macro clojure:

(defmacro coalesce 
    ([] nil) 
    ([x] x) 
    ([x & next] 
     `(let [v# ~x] 
      (if (not (nil? v#)) v# (coalesce [email protected]))))) 
+1

@ user128186: Non sono sicuro che sia necessario il '(non (nil? V #))' nell'istruzione '(if ...)', dal momento che tutto ciò che non è 'false' o' nil' viene valutato come 'true'. Altrimenti le nostre soluzioni sono le stesse. – Ralph

+3

perché dovresti eseguire una riscrittura 1: 1 del "o" -macro? – nickik

+2

@Ralph: Quindi prima non vuoi nil o il primo valore non falso? – Arjan

21

Quello che vuoi è la "o" macro.

Valuta expr uno alla volta, da sinistra a destra. Se un modulo restituisce un valore true logico o restituisce tale valore e non valuta nessuna delle altre espressioni, altrimenti restituisce il valore dell'ultima espressione. (o) restituisce nil.

http://clojuredocs.org/clojure_core/clojure.core/or

Se desideri solo nil e non falsa fare una riscrittura e di fondersi e il nome.

Edit:

Questo non poteva essere fatto in funzione perché le funzioni valutare tutti i loro argomenti prima. Questo potrebbe essere fatto in Haskell perché le funzioni sono pigre (non sicuro al 100% della cosa di Haskell).

+0

Voglio il ** primo ** valore non nullo, non l'ultimo, tuttavia '(o ...)' fa quello che mi serve. Non ho capito che '(e ...)' e '(o ...)' restituiscono i valori. Pensavo che restituissero il falso o il vero. Ma anche questi non restituiscono il valore che voglio per un input di 'false'. – Ralph

+0

oh sicuro sto andando a cambiare. – nickik

3

Si potrebbe utilizzare tenere introdotto in 1.2:

EDIT: esteso rispondere un po '. Macro per invocazioni dirette. Helper per es. applica + pigro seq producendo i valori.

(defn coalesce* 
    [values] 
    (first (keep identity values))) 

(defmacro coalesce 
    [& values] 
    `(coalesce* (lazy-list [email protected]))) 

Tuttavia per evitare la valutazione dei valori è necessario un percorso interno.

Ugly:

(lazy-cat [e1] [e2] [e3])

Un po 'più coinvolto, ma più bella nel codice:

(defn lazy-list* 
    [& delayed-values] 
    (when-let [delayed-values (seq delayed-values)] 
    (reify 
     clojure.lang.ISeq 
     (first [this] @(first delayed-values)) 
     (next [this] (lazy-list* (next delayed-values))) 
     (more [this] (or (next this)()))))) 

(defmacro lazy-list 
    [& values] 
    `(lazy-list* [email protected](map (fn [v] `(delay ~v)) values)) 
+0

Posso capire perché una soluzione non macro potrebbe essere migliore, poiché la soluzione macro non può essere composta con altre funzioni. – Ralph

+0

@ralph: Ovviamente la soluzione accettata è più veloce, ma la mia soluzione è più flessibile. Quello che dovresti scegliere dipende dalle tue esigenze. Se non hai bisogno di velocità, ma hai una sequenza creata pigramente che vuoi fondere, la mia soluzione fa il trucco. Se hai bisogno di una gestione veloce di pochi valori noti. quindi la soluzione di Arjan in soccorso. YMMV. :) – kotarak

+0

Non sto criticando. In ogni caso è più un esercizio accademico. Stavo pensando a come implementare l'operatore "elvis" in Scala, e mi ha fatto pensare a qualcosa di simile in Clojure. – Ralph

0

Forse sto capendo male la domanda, ma questo non è solo il primo elemento filtrato?

Esempio:

 
user=> (first (filter (complement nil?) [nil false :foo])) 
false 
user=> (first (filter (complement nil?) [nil :foo])) 
:foo 
user=> (first (filter (complement nil?) [])) 
nil 
user=> (first (filter (complement nil?) nil)) 
nil 

Potrebbe essere ridotto fino a:

 
(defn coalesce [& vals] 
    (first (filter (complement nil?) vals))) 
 
user=> (coalesce nil false :foo) 
false 
user=> (coalesce nil :foo) 
:foo 
user=> (coalesce nil) 
nil 
user=> (coalesce) 
nil 
+0

Questo è un altro modo seq-y. Un terzo sarebbe '(primo (rimuovere nil? ...))'. Tuttavia questo non risolve il problema che le espressioni da coalescere dovrebbero essere valutate solo in base alle necessità. Con cose come '(ripetutamente # (generate-qualcosa))' questo funziona alla scatola, ma non per valori "letterali": '[(do-something) (do-otherthing) (do-thirdthing)]'. Qui tutto viene valutato prima che il filtro lo veda. – kotarak

+1

Sì, ho perso la valutazione lazy del requisito degli arg. –

1

Alcune versioni di funzione del fondono, se si preferisce evitare le macro:

(defn coalesce 
    "Returns first non-nil argument." 
    [& args] 
    (first (keep identity args))) 

(defn coalesce-with 
    "Returns first argument which passes f." 
    [f & args] 
    (first (filter f args))) 

Uso:

=> (coalesce nil "a" "b") 
"a" 
=> (coalesce-with not-empty nil "" "123") 
"123" 

A differenza delle specifiche, questo valuterà tutti gli argomenti. Utilizzare or o un'altra soluzione macro appropriata se si desidera una valutazione del cortocircuito.