通常情况下,我有一个函数,它接受一些可选参数并将它们传递给其他函数,这些函数将它们进一步传递到堆栈中,依此类推。如何才能在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,我想在任何“级别”调用任何函数,而不考虑是否有其他函数调用它。有一个统一的调用约定很有帮助。(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下。
如果直接传递可选参数变量,则被调用者不能将其作为可选参数接受
这不是严格意义上的,只是当两个函数都采用可变参数 - 在你的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定义。