замыкающий макрос с ошибкой - PullRequest
2 голосов
/ 20 марта 2019

У меня есть несколько операций, которые я хочу добавить в поток, где каждая может завершиться неудачей. Я бы предпочел получить ошибку как значение, а не использовать try-catch, который нарушает поток выполнения.

Я могу сделать наивную версию и заставить мои функции использовать nil в качестве ошибки:

(if-let (op1 ...)
  (if-let (op2 ...)
    ...
    err1)
  err2)

но это вложенное и затрудняет чтение.

Я мог бы использовать some->, который кажется самым близким решением, но он не говорит, что не удалось:

(if-let [res (some-> arg
                     op1
                     op2)]
  res
  somethin-failed) ;; what failed though?

Я также посмотрел на -> и cond->, но, похоже, они не помогают.

Я знаю, что в Интернете есть макросы для подобных вещей, но я бы не стал добавлять макросы, если что-то существует для решения этой проблемы. Надеюсь, есть что-то вроде:

(some-with-err-> arg
                 op1 err1
                 op2 err2
                 ...)

Возможно, я упускаю из виду нечто более простое, но я не могу найти что-то встроенное для решения этой проблемы.

Я могу написать макрос, чтобы сделать это, но сейчас лучше его избегать.

1 Ответ

3 голосов
/ 20 марта 2019

Для этого нет ничего встроенного, но есть библиотеки для обработки монадических ошибок (например, Failjure ), которая выглядит как то, что вы ищете.

Вы можете получить версию some-with-err-> из определения макроса some->. Единственным практическим отличием является функция map, которая связывается с steps, теперь разделяет значения форм / ошибок, переносит вызовы step в try и возвращает карту пространства имен при ошибке:

(defmacro some-with-err->
  [expr & forms]
  {:pre [(even? (count forms))]}
  (let [g (gensym)
        steps (map (fn [[step error]]
                     `(if (or (nil? ~g) (::error ~g))
                        ~g
                        (try (-> ~g ~step)
                             (catch Exception _# {::error ~error}))))
                   (partition 2 forms))]
    `(let [~g ~expr
           ~@(interleave (repeat g) (butlast steps))]
       ~(if (empty? steps)
          g
          (last steps)))))

Может использоваться как some->, но каждая форма должна сопровождаться возвращаемым значением ошибки:

(some-with-err-> 1
  (+ 1) :addition
  (/ 0) :division
  (* 2) :multiplication)
=> #:user{:error :division}

(some-with-err-> " "
  (clojure.string/trim) :trim
  (not-empty) :empty
  (str "foo") :append)
=> nil ;; from not-empty
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...