如何转发可选参数

时间:2018-02-11 22:47:56

标签: clojure parameter-passing optional-parameters

通常情况下,我有一个函数,它接受一些可选参数并将它们传递给其他函数,这些函数将它们进一步传递到堆栈中,依此类推。如何在Clojure中完成,而不会出现我在下面说明的容易出错的复杂类型?

  1. 如果直接传递可选参数变量,则被调用者不能将其作为可选参数接受:

    (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)
    
  2. 如果调用者需要可选参数作为非可选的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,我想在任何“级别”调用任何函数,而不考虑是否有其他函数调用它。有一个统一的调用约定很有帮助。

  3. 如果转发一个未被调用者提供的可选参数,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))
    
  4. 对我来说,大多数Clojure功能都非常顺利,但事实并非如此。什么是正确的方法?

    以上全部属于Clojure 1.9.0。

1 个答案:

答案 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定义。