Clojure 如何转发可选参数

Clojure 如何转发可选参数,clojure,parameter-passing,optional-parameters,Clojure,Parameter Passing,Optional Parameters,经常发生的情况是,我有一个函数,它接受一些可选参数并将它们传递给其他函数,这些函数将它们进一步传递给堆栈,以此类推。在Clojure中如何做到这一点,而不存在我在下面说明的容易出错的复杂性类型 如果直接传递可选参数变量,则被调用方无法将其作为可选参数接受: (defn func1 [& {:keys [n-iterations] :or {n-iterations 20} :as opts}] (println "func1:" n-iterations) (func2 opts

经常发生的情况是,我有一个函数,它接受一些可选参数并将它们传递给其他函数,这些函数将它们进一步传递给堆栈,以此类推。在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中

    如果直接传递可选参数变量,则被调用方无法将其作为可选参数接受

    严格来说,这不是真的,只是当两个函数在您的case关键字args中都采用可变参数,并且您将它们分解为一个映射时,您必须
    将它们应用到其他可变函数,就像您将映射应用到它们一样:

    (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
    ,这就是您的示例中实际发生的情况

    如果调用者需要可选参数作为非可选映射参数,这很难看而且容易出错,而且会丢失默认值

    在这种情况下,您仍然可以使用
    :或
    来分解结构

    (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
    
    这就是为什么有必要
    可变的、分解为集合的参数应用于其他可变函数的原因

    还有一个选择:

    (defn foo [x y z & [{:keys [a b c}]] ...)
    
    这是可变的,但在第一个可选arg位置采用可选映射

    <>你也可以考虑使用函数的多个固定的定义。