2010-11-03 6 views
13

SQL предлагает функцию с именем coalesce(a, b, c, ...), которая возвращает значение null, если все его аргументы равны нулю, в противном случае он возвращает первый ненулевой аргумент.Clojure coalesce function

Как вы собираетесь писать что-то подобное в Clojure?

Он будет называться так: (coalesce f1 f2 f3 ...) где fi являются формами , которые должны быть оценены только в случае необходимости. Если f1 не равен нулю, то f2 не следует оценивать - он может иметь побочные эффекты.

Возможно, Clojure уже предлагает такую ​​функцию (или макрос).

EDIT: Вот решение, которое я придумал (редактировался программирования Стюарта Халлоуэй в Clojure, (and ...) макросъемки на странице 206):

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

похоже на работу.

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

Исправлено.

ответ

5

На основании ответа nickik и "или" Clojure макрос:

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

@ user128186: Не уверен, что вам нужно '(not (nil? V #))' в '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' неправильно. В противном случае наши решения будут одинаковыми. – Ralph

+3

Зачем вам делать переписку 1: 1 из «или» -макро? – nickik

+2

@Ralph: Значит, вы хотите, чтобы сначала не ноль или первое не ложное значение? – Arjan

21

Что вы хотите, это макрос или.

Оценки exprs по одному, слева направо. Если форма возвращает логическое истинное значение или возвращает это значение, а не оценивает любое из других выражений, в противном случае оно возвращает значение последнего выражения. (или) возвращает нуль.

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

Если вы хотите только ноль и не ложь делать перезапись и и назовите его слипаются.

Edit:

Это не может быть сделано в качестве функции, поскольку функции оценить все их аргументы первой. Это можно сделать в Haskell, потому что функции ленивы (не на 100% уверены в вещи Haskell).

+0

Я хочу ** первое ** значение, отличное от нуля, но не последнее, однако '(или ...)' делает то, что мне нужно. Я не понимал, что '(и ...)' и '(или ...)' возвращают значения. Я думал, что они вернули мне ложь или правда. Но даже они не возвращают значение, которое я хочу для ввода «false». – Ralph

+0

О, конечно, я собираюсь изменить это. – nickik

3

Вы можете использовать снова введены в 1.2:

EDIT: расширенный ответ немного. Макрос для прямых вызовов. Помощник, например. Примените + lazy seq, производя значения.

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

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

Однако для предотвращения оценки ценностей необходим какой-то домашний образ жизни.

Гадкий:

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

Немного сложнее, но симпатичнее в коде:

(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

Я могу понять, почему решение, отличное от макросов, может быть лучше, так как макро-решение не может быть составлено с другими функциями. – Ralph

+0

@ralph: Конечно, принятое решение выполняется быстрее, но мое решение более гибкое. То, что вы должны выбрать, зависит от ваших потребностей. Если вам не нужна скорость, но есть лениво созданная последовательность, которую вы хотите объединить, мое решение делает трюк. Если вам нужна быстрая обработка нескольких известных значений. то решение арджана спасению. YMMV. :) – kotarak

+0

Я не критикую. В любом случае, это академическое упражнение. Я думал о том, как реализовать «elvis» оператора в Scala, и это заставило меня задуматься о чем-то подобном в Clojure. – Ralph

0

Возможно, я 'mis misprehending вопрос, но разве это не только первый фильтрованный элемент?

Например:

 
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 

Это может быть сокращен до:

 
(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

Это еще один путь seq-y. Третьим будет '(сначала (удалить nil? ...))'. Однако это не решает проблему, заключающуюся в том, что выражения, которые должны быть объединены, должны оцениваться только по необходимости. С такими вещами, как '(повторно # (генерация-что-то))' это работает в поле, но не для «буквальных» значений: '[(do-something) (do-otherthing) (do-thirdthing)]'. Здесь все оценивается до того, как фильтр увидит его. – kotarak

+1

Да, я пропустил ленивую оценку требования Арца. –

1

Некоторые функции версии сливаются, если вы не хотите избежать макросов:

(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))) 

Использование:

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

В отличие от спецификации, это будет оценивать все аргументы. Используйте or или другое подходящее решение для макросов, если вы хотите получить оценку короткого замыкания.