经常发生这样的情况:我有一个函数,它接受一些可选参数并将它们传递给其他函数,这些函数将它们进一步传递到堆栈,依此类推。如何在 Clojure 中完成此操作,而不出现我下面说明的容易出错的复杂性类型?
如果直接传递可选参数变量,被调用者不能接受它作为可选参数:
(defn func1 [& {:keys [n-iterations] :or {n-iterations 20} :as opts}] (println "func1:" n-iterations) (func2 opts)) (defn func2 [& {:keys [n-iterations]}] (println "func2:" n-iterations)) user=> (func1 :n-iterations 15) func1: 15 IllegalArgumentException No value supplied for key: {:n-iterations 15} clojure.lang.PersistentHashMap.create (PersistentHashMap.java:77)
如果调用者需要可选参数作为非可选映射参数,这是丑陋且容易出错的,而且最重要的是,它会丢失默认值:
(defn func2 [{:keys [n-iterations]}] ;lost the & (println "func2:" n-iterations)) user=> (func1 :n-iterations 15) func1: 15 func2: 15 nil user=> (func1) func1: 20 func2: nil nil
我听说您应该在顶层采用可选参数,并在堆栈中的所有较低级别采用非可选映射。但我发现这并不令人满意,因为通常,特别是在 REPL 上,我想在任何“级别”调用任何函数,而不考虑其他函数是否调用它。拥有统一的调用约定很有帮助。
如果您转发调用者未提供的可选参数,Clojure 会将其转换为 nil,然后在堆栈中的每一步将其包装在 ArraySeq 中:
(defn func1 [& opts] (println "func1:" opts) (func2 opts)) (defn func2 [& opts] (println (type opts)) (println "func2:" opts) (func3 opts)) (defn func3 [& opts] (println "func3:" opts)) user=> (func1) func1: nil func2: (nil) func3: ((nil))
大多数 Clojure 功能对我来说都运行得相当顺利,但这个却不是。正确的做法是什么?
以上都是在 Clojure 1.9.0 下进行的。
最佳答案
If you pass the optional-argument variable directly, the callee can't accept it as optional arguments
这并不完全正确,只是当两个函数都采用可变参数(在您的例子中是关键字参数)并且您将它们解构为单个映射时,那么您必须将它们应用
到其他可变参数函数的使用方式与将 map 应用于它们的方式相同:
(defn func2 [& {:keys [n-iterations]}]
(println "func2:" n-iterations))
(defn func1 [& {:keys [n-iterations]
:or {n-iterations 20}
:as opts}]
(println "func1:" n-iterations)
(apply func2 (mapcat identity opts)))
(func1 :n-iterations 15) ;; works fine
您也不能像 (func2 {:n-iterations 20})
那样直接调用 func2
,这实际上就是您的示例中发生的情况。
If the caller requires the optional arguments as a non-optional map argument, that's ugly and error-prone, and on top of that, it loses the defaults
在这种情况下,您仍然可以使用 :or
进行解构。
(defn func2 [{:keys [n-iterations]
:or {n-iterations 10}}]
(println "func2:" n-iterations))
(func2 nil) ;; prints "func2: 10"
If you forward an optional argument that's not supplied by the caller, Clojure turns it into a nil and then wraps it in an ArraySeq on each step down the stack
我认为这只是对可变参数和解构如何工作的误解。在每个函数中,您都接受可变参数并将它们绑定(bind)到单个名称 opts
。在函数体内,opts
是一个集合。当您使用 opts
作为唯一参数调用其他可变参数函数时,您将它们作为一元函数进行调用。像这样看:
(foo [1 2 3]) ;; this is the call style you're getting
(foo 1 2 3) ;; this is the call style you want
(apply foo [1 2 3]) ;; how to call `foo` with a coll variadic-ly
这就是为什么有必要将可变参数、解构为集合参数应用
到其他可变参数函数。
还有另一种选择:
(defn foo [x y z & [{:keys [a b c}]] ...)
这是可变参数,但在第一个可选参数位置采用可选映射。
您还可以考虑按照建议使用多个固定数量的函数定义 here .
关于clojure - 如何转发可选参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48737318/