通常情况下,我有一个函数,它接受一些可选参数并将它们传递给其他函数,这些函数将它们进一步传递到堆栈中,依此类推。如何在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)
如果调用者需要可选参数作为非可选的map参数,那就是丑陋且容易出错,最重要的是,它会丢失默认值:
(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。
答案 0 :(得分:2)
如果直接传递optional-argument变量,则被调用者不能将其作为可选参数
接受
这不是严格意义上的,只是当两个函数都采用可变参数时 - 在你的case关键字args中 - 你将它们解构成一个映射,那么你必须apply
它们到其他的可变函数以同样的方式将地图应用于他们:
(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
那样直接致电(func2 {:n-iterations 20})
,这实际上就是您示例中发生的情况。
如果调用者需要可选参数作为非可选的map参数,那就是丑陋且容易出错,并且最重要的是,它会丢失默认值
在这种情况下,您仍然可以使用:or
进行解构。
(defn func2 [{:keys [n-iterations]
:or {n-iterations 10}}]
(println "func2:" n-iterations))
(func2 nil) ;; prints "func2: 10"
如果转发一个不是由调用者提供的可选参数,Clojure会将其转换为nil,然后在堆栈的每一步中将其包装在ArraySeq中
我认为这只是对可变参数和解构如何工作的误解。在每个函数中,您接受可变参数并将它们绑定到单个名称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
这就是为什么有必要apply
你的可变参数,变构到其他可变函数的集合参数。
还有另一种选择:
(defn foo [x y z & [{:keys [a b c}]] ...)
这是可变参数,但在第一个可选的arg位置采用可选的映射。
您还可以考虑按建议here使用函数的多个固定arm定义。