在Clojure函数中检查多个if的更好方法?

时间:2018-01-28 20:27:26

标签: if-statement clojure idiomatic

我有一个类似于以下内容的Clojure函数。

(defn calculate-stuff [data]
  (if (some-simple-validation data)
    (create-error data)
    (let [foo (calculate-stuff-using data)]
      (if (failed? foo)
        (create-error foo)
        (let [bar (calculate-more-stuff-using foo)]
          (if (failed? bar)
            (create-error bar)
            (calculate-response bar)))))))

哪种方法很好但是有点难以阅读,所以我想知道是否有更惯用的Clojure写作方式?

我考虑过让some-simple-validationcalculate-stuff-usingcalculate-more-stuff-using抛出异常并使用try / catch块,但感觉就像使用控件流的异常一样,感觉不正确。< / p>

我不能让异常逃避这个功能,因为我用它来映射一系列地图,我仍然想继续处理其余部分。

我想我之后的事情是这样的?

(defn calculate-stuff [data]
  (let-with-checking-function
    [valid-data (some-simple-validation data)
     foo (calculate-stuff-using valid-data)
     bar (calculate-more-stuff-using foo)]
    failed?)                    ; this function is used to check each variable
      (create-error %)          ; % is the variable that failed
      (calculate-response bar)) ; all variables are OK

谢谢!

5 个答案:

答案 0 :(得分:6)

如果验证失败指示错误情况,则异常(和try-catch块)可能是处理错误的最佳方式。特别是如果它不是正常的&#34;出现(即无效的遗嘱等)。

更多&#34;正常&#34;但仍然无效&#34;在某些情况下,您可以使用some->(发音为&#34; some-thread&#34;)来静静地压制&#34;坏&#34;案例。只需让验证器返回nil以查找错误数据,some->将中止处理链:

(defn proc-num [n]
  (when (number? n)
    (println :proc-num n)
    n))

(defn proc-int [n]
  (when (int? n)
    (println :proc-int n)
    n))

(defn proc-odd [n]
  (when (odd? n)
    (println :proc-odd n)
    n))

(defn proc-ten [n]
  (when (< 10 n)
    (println :proc-10 n)
    n))

(defn process [arg]
  (when (nil? arg)
    (throw (ex-info "Cannot have nil data" {:arg arg})))
  (some-> arg
    proc-num
    proc-int
    proc-odd
    proc-ten))

结果:

(process :a) => nil

(process "foo") => nil

:proc-num 12
:proc-int 12
(process 12) => nil

:proc-num 13
:proc-int 13
:proc-odd 13
:proc-10 13
(process 13) => 13

(throws? (process nil)) => true

话虽如此,您现在正在使用nil表示&#34;数据验证失败&#34;,因此您的数据中不能包含nil

使用无效数据的例外

使用nil作为短路处理的特殊值可以起作用,但使用普通旧例外可能更容易,特别是对于明显&#34;错误数据的情况&#34;:< / p>

(defn parse-with-default [str-val default-val]
  (try
    (Long/parseLong str-val)
    (catch Exception e
      default-val))) ; default value

(parse-with-default "66-Six" 42) => 42

a little macro to automate this process名为with-exception-default

(defn proc-num [n]
  (when-not (number? n)
    (throw (IllegalArgumentException. "Not a number")))
  n)

(defn proc-int [n]
  (when-not (int? n)
    (throw (IllegalArgumentException. "Not int")))
  n)

(defn proc-odd [n]
  (when-not (odd? n)
    (throw (IllegalArgumentException. "Not odd")))
  n)

(defn proc-ten [n]
  (when-not (< 10 n)
    (throw (IllegalArgumentException. "Not big enough")))
  n)

(defn process [arg]
  (with-exception-default 42  ; <= default value to return if anything fails
    (-> arg
      proc-num
      proc-int
      proc-odd
      proc-ten)))

(process nil)    => 42
(process :a)     => 42
(process "foo")  => 42
(process 12)     => 42

(process 13)     => 13

这避免给nil或任何其他&#34; sentinal&#34;赋予特殊含义。值,并使用Exception用于在出现错误时更改控制流的正常目的。

答案 1 :(得分:2)

这是Clojure代码库的常见问题。一种方法是将数据包装成提供更多信息的内容,即操作是否成功。有几个库可以帮助您。

例如对猫(http://funcool.github.io/cats/latest/):

(m/mlet [a (maybe/just 1)
         b (maybe/just (inc a))]
  (m/return (* a b)))

或者结果 - 我帮助了这个(https://github.com/clanhr/result):

(result/enforce-let [r1 notgood
                     r2 foo])
    (println "notgoof will be returned"))

答案 2 :(得分:1)

其他答案中的一个示例使用具有缺陷的some->宏:每次失败都应该将消息打印到控制台并返回nil。这并不好,因为nil值也可能表示良好的结果,特别是对于空集合。毋庸置疑,您不仅需要打印错误,还要以某种方式处理错误或将其记录在某处。

重构代码的最简单方法就是分解代码。比方说,你可以将第一个if的负分支中的所有内容放入一个单独的函数中,就是这样。这两个函数将变得更容易测试和调试。

至于我,这将是最好的选择,因为它会立即解决问题。

有例外情况也很好。不要发明自己的异常类,只需使用ex-info抛出地图。一旦被捕获,这样的异常将返回与其一起抛出的所有数据:

(if (some-checks data)
  (some-positive-code data)
  (throw (ex-into "Some useful message" {:type :error 
                                         :data data})))

抓住它:

(try
  (some-validation data)
(catch Exception e
  (let [err-data (ex-data e)]
    ; ...)))

最后,可能存在使用monad的情况,但要注意过度设计问题。

答案 3 :(得分:0)

我遇到了同样的问题。我的解决方案是复制some-&gt;&gt;宏并调整一下:

(defmacro run-until->> [stop? expr & forms]
     (let [g (gensym)
           steps (map (fn [step] `(if (~stop? ~g) ~g (->> ~g ~step)))
               forms)]
        `(let [~g ~expr
               ~@(interleave (repeat g) (butlast steps))]
             ~(if (empty? steps)
                g
                (last steps)))))

而不是检查nils,此宏将检查您的预定义条件。例如:

(defn validate-data [[status data]]
    (if (< (:a data) 10)
       [:validated data]
       [:failed data]))

(defn calculate-1 [[status data]]
     [:calculate-1 (assoc data :b 2)])

(defn calculate-2 [[status data]]
    (if (:b data)
       [:calculate-2 (update data :b inc)]
       [:failed data]))

(deftest test
    (let [initial-data [:init {:a 1}]]
       (is (= [:calculate-2 {:a 1, :b 3}] 
              (run-until->> #(= :failed (first %))
                            initial-data
                            (validate-data)
                            (calculate-1)
                            (calculate-2))))

       (is (= [:failed {:a 1}] 
              (run-until->> #(= :failed (first %))
                            initial-data
                            (validate-data)
                            (calculate-2))))))

答案 4 :(得分:0)

我创建了Promenade来处理这种情况。