If statement 在Clojure函数中写入多个if检查的更好方法?
我有一个Clojure函数,它看起来像下面这样If statement 在Clojure函数中写入多个if检查的更好方法?,if-statement,clojure,idioms,If Statement,Clojure,Idioms,我有一个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-us
(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写作方式
我考虑过做一些简单的验证
,使用
计算东西,以及使用
抛出异常和使用try/catch块计算更多东西,但这感觉像是对感觉不正确的控制流使用异常
我也不能让异常逃逸这个函数,因为我正在使用它映射一系列映射,并且我仍然希望继续处理剩余的映射
我想我想要的是这样的东西
(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
谢谢 如果验证失败表明存在错误情况,则异常(和try-catch块)可能是处理它的最佳方式。特别是如果不是“正常”情况(即无效的客户id等) 对于更多“正常”但仍然“无效”的情况,您可以使用
some->
(发音为“some-thread”)安静地消除“坏”情况。只要让验证器返回坏数据的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))
话虽如此,您现在使用nil
表示“数据验证失败”,因此数据中不能包含nil
对无效数据使用异常 使用
nil
作为短路处理的特殊值可以起作用,但使用普通的旧异常可能更容易,特别是对于明显是“坏数据”的情况:
我调用了,默认情况除外
:
(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
(defn proc num[n]
(如果不是(编号?n)
(抛出(IllegalArgumentException.“不是数字”))
n)
(defn proc int[n]
(如果不是(int?n)
(抛出(IllegalArgumentException.“Not int”))
n)
(defn proc奇数[n]
(如果不是(奇数?n)
(抛出(IllegalArgumentException.“非奇数”))
n)
(定义过程十[n]
(如果不是(<10 n)
(抛出(IllegalArgumentException.“不够大”))
n)
(defn进程[arg]
(默认值为42;arg除外)
进程数
进程整型
进程奇数
过程(十)
(过程零)=>42
(过程:a)=>42
(处理“foo”)=>42
(过程12)=>42
(过程13)=>13
这避免了对
nil
或任何其他“sentinal”值赋予特殊含义,并使用异常
用于在出现错误时更改控制流的正常目的。这是Clojure代码库中的常见问题。一种方法是将数据包装成提供更多信息的内容,即操作是否成功。有几个库可以帮助您实现这一点
例如猫():
或者有结果-我在这一点上提供了帮助():
我也面临同样的问题。我的解决方案是复制some->>宏并稍微调整它:
(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))))))
(defn验证数据[[状态数据]]
(如果(<(:a数据)10)
[:验证数据]
[:失败的数据])
(定义计算-1[[状态数据]]
[:计算-1(关联数据:B2)])
(定义计算-2[[状态数据]]
(如果(:b数据)
[:calculate-2(更新数据:b inc)]
[:失败的数据])
(除雾试验
(让[初始数据[:init{:a1}]]
(是(=[:calculate-2{:a1,:b3}]
(运行到->>#(=:失败(第一个%)
初始数据
(验证数据)
(计算-1)
(计算-2)
(是(=[:失败的{:a 1}]
(运行到->>#(=:失败(第一个%)
初始数据
(验证数据)
(计算-2(()()))
其他答案中的一个示例使用了有缺陷的某些->
宏:每次失败都应将消息打印到控制台并返回nil
。这是不好的,因为nil
值也可能指示良好的结果,尤其是对于空集合。不用说,您不仅需要打印错误,还需要以某种方式处理错误或将其记录在某个地方
重构代码最简单的方法就是分解代码。比方说,您可以将第一个if
的负分支的所有内容放在一个单独的函数中,就是这样。这两个函数将更易于测试和调试
对我来说,这将是最好的选择,因为它将立即解决问题
有例外的情况也是好的。不要发明自己的异常类,只需使用exinfo
抛出一个映射即可。一旦捕获,此类异常将返回与其一起抛出的所有数据:
(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)]
; ...)))
最后,可能有使用单子的情况,但要注意过度设计问题。我创建单子就是为了处理这种情况。这是我在许多情况下开始做的。它最终的效果很像哈斯克尔的链锁。一旦它在链的某个地方失败,整个过程就会失败并返回零。最后我使用了异常,感谢您的建议奇妙的答案-清晰而直接。谢谢你!另见。
(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)))))
(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))))))
(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)]
; ...)))