2010-11-03 25 views
13

SQL ofrece una función llamada coalesce(a, b, c, ...) que devuelve null si todos sus argumentos son nulos, de lo contrario, devuelve el primer argumento no nulo.Clojure coalesce function

¿Cómo harías para escribir algo así en Clojure?

Se llamará así: (coalesce f1 f2 f3 ...) donde el fi son formas que sólo se deben evaluar si se requiere. Si f1 no es nulo, entonces no se debe evaluar f2, puede tener efectos secundarios.

Quizás Clojure ya ofrezca dicha función (o macro).

EDITAR: Aquí una solución que se me ocurrió (modificado de Programación de Stuart Halloway Clojure, (and ...) macro en la página 206):

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

parece funcionar.

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

Fijo.

Respuesta

5

Sobre la base de la respuesta de nickik y "o" macro clojure:

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

@ user128186: No estoy seguro de que necesite el '(not (nil? V #))' en la declaración '(if ...)', ya que cualquier cosa que no sea 'false' o' nil' se evalúa como 'true'. De lo contrario, nuestras soluciones son las mismas. – Ralph

+3

¿por qué harías una reescritura 1: 1 del "o" -macro? – nickik

+2

@Ralph: ¿Entonces quieres primero el valor nulo o el primero no falso? – Arjan

21

Lo que desea es la macro "o".

Evalúa exprs de a una por vez, de izquierda a derecha. Si un formulario devuelve un valor verdadero lógico, o devuelve ese valor y no evalúa cualquiera de las otras expresiones, de lo contrario, devuelve el valor de la última expresión. (o) devuelve nil.

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

Si sólo desea nula y no falsas hacer una reescritura de e y el nombre que se aglutinan.

Editar:

Esto no podría hacerse como una función porque las funciones evalúan todos sus argumentos en primer lugar. Esto podría hacerse en Haskell porque las funciones son flojas (no es 100% seguro de lo de Haskell).

+0

Quiero el ** primer ** valor nulo, no el último, sin embargo '(o ...)' hace lo que necesito. No me di cuenta de que '(y ...)' y '(o ...)' devuelven los valores. Pensé que volvieron falso o verdadero. Pero incluso estos no devuelven el valor que quiero para una entrada de 'falso'. – Ralph

+0

oh seguro voy a cambiar eso. – nickik

3

Usted podría utilizar Mantener introducido en 1.2:

EDITAR: extensión contestar un poco. Macro para invociones directas. Ayudante por ej. aplicar + lazy seq produciendo los valores.

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

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

Sin embargo, para evitar la evaluación de los valores, se necesita una forma tradicional.

feo:

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

un poco más complicado, pero más bonita en el código:

(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

Veo por qué una solución no macro podría ser mejor, ya que la macro solución no se puede componer con otras funciones. – Ralph

+0

@ralph: Por supuesto, la solución aceptada es más rápida, pero mi solución es más flexible. Lo que debe elegir depende de sus necesidades. Si no necesita velocidad, pero tiene una secuencia creada perezosamente que desea fusionar, mi solución funciona. Si necesita un manejo rápido de pocos valores conocidos. entonces la solución de Arjan para el rescate. YMMV. :) – kotarak

+0

No estoy criticando. Es más un ejercicio académico de todos modos. Estaba pensando en cómo implementar el operador "elvis" en Scala, y me hizo pensar en algo similar en Clojure. – Ralph

0

Tal vez Estoy malinterpretando la pregunta, pero ¿no es este solo el primer elemento filtrado?

Ej:

 
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 

Podría ser acortado hasta:

 
(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

Esta es otra forma seq-y. Un tercero sería '(first (remove nil? ...))'. Sin embargo, esto no resuelve el problema de que las expresiones que se combinarán solo se deben evaluar según la necesidad. Con cosas como '(repetidamente # (generar-algo))' esto funciona en el cuadro, pero no para valores "literales": '[(do-something) (do-otherthing) (do-thirdthing)]'. Aquí todo se evalúa antes de que el filtro lo vea. – kotarak

+1

Sí, me perdí la evaluación perezosa del requisito de args. –

1

Algunas versiones de la función de coalescencia, si usted prefiere evitar las macros:

(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 diferencia de la especificación, esto evaluará todos los argumentos. Use or u otra solución de macro apropiada si desea una evaluación de cortocircuito.