Clojure функция объединения - PullRequest
       19

Clojure функция объединения

13 голосов
/ 03 ноября 2010

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

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

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

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

РЕДАКТИРОВАТЬ : Вот решение, которое я придумала (модифицировано из Clojure Programming Clojure Стюарта, макрос (and ...) на странице 206):

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

Кажется, работает.

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

Исправлено.

Ответы [ 5 ]

22 голосов
/ 03 ноября 2010

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

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

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

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

Редактировать:

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

4 голосов
/ 03 ноября 2010

Основано на ответе nickik и макросе "или" clojure:

(defmacro coalesce
    ([] nil)
    ([x] x)
    ([x & next]
       `(let [v# ~x]
           (if (not (nil? v#)) v# (coalesce ~@next)))))
3 голосов
/ 03 ноября 2010

Вы могли бы использовать Keep в версии 1.2:

РЕДАКТИРОВАТЬ: расширенный ответ немного. Макрос для прямых вызовов. Помощник например. apply + lazy seq для получения значений.

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

(defmacro coalesce
  [& values]
  `(coalesce* (lazy-list ~@values)))

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

Гадкий:

(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* ~@(map (fn [v] `(delay ~v)) values))
2 голосов
/ 03 декабря 2015

Некоторые версии функций coalesce, если вы предпочитаете избегать макросов:

(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 или другое подходящее решение для макросов, если вы хотите выполнить оценку короткого замыкания.

0 голосов
/ 04 ноября 2010

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

например:.

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
...