【问题标题】:How to forward optional arguments如何转发可选参数
【发布时间】:2018-07-22 01:58:50
【问题描述】:

经常发生的情况是,我有一个函数,它接受一些可选参数并将它们传递给其他函数,这些函数将它们进一步向下传递到堆栈,依此类推。如果没有我在下面说明的容易出错的复杂性类型,如何在 Clojure 中做到这一点?

  1. 如果你直接传递 optional-argument 变量,被调用者不能接受它作为可选参数:

    (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))
    

大多数 Clojure 功能对我来说都非常顺利,但事实并非如此。这样做的正确方法是什么?

以上都是Clojure 1.9.0下的。

【问题讨论】:

    标签: clojure parameter-passing optional-parameters


    【解决方案1】:

    如果直接传递 optional-argument 变量,被调用者不能接受它作为可选参数

    这并不完全正确,只是当两个函数都采用可变参数(在您的情况下为关键字 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 {:n-iterations 20}) 那样直接调用 func2,这实际上就是您的示例中正在发生的事情。

    如果调用者需要可选参数作为非可选的 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 作为唯一参数调用其他可变参数函数时,您将它们作为 unary 函数调用。像这样看:

    (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 的建议使用多个固定数量的函数定义。

    【讨论】:

      猜你喜欢
      • 2021-01-24
      • 2014-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-15
      • 2012-12-20
      相关资源
      最近更新 更多