clojure - 如何转发可选参数

标签 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. 如果调用者需要可选参数作为非可选映射参数,这是丑陋且容易出错的,而且最重要的是,它会丢失默认值:

    (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 下进行的。

最佳答案

If you pass the optional-argument variable directly, the callee can't accept it as optional arguments

这并不完全正确,只是当两个函数都采用可变参数(在您的例子中是关键字参数)并且您将它们解构为单个映射时,那么您必须将它们应用到其他可变参数函数的使用方式与将 map 应用于它们的方式相同:

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

If the caller requires the optional arguments as a non-optional map argument, that's ugly and error-prone, and on top of that, it loses the defaults

在这种情况下,您仍然可以使用 :or 进行解构。

(defn func2 [{:keys [n-iterations]
              :or {n-iterations 10}}]
  (println "func2:" n-iterations))
(func2 nil) ;; prints "func2: 10"

If you forward an optional argument that's not supplied by the caller, Clojure turns it into a nil and then wraps it in an ArraySeq on each step down the stack

我认为这只是对可变参数和解构如何工作的误解。在每个函数中,您都接受可变参数并将它们绑定(bind)到单个名称 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}]] ...)

这是可变参数,但在第一个可选参数位置采用可选映射。

您还可以考虑按照建议使用多个固定数量的函数定义 here .

关于clojure - 如何转发可选参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48737318/

相关文章:

clojure - 如何从 clojure/cider/nrepl 中的同级文件导入内容?

java - Java 中 .NET 的 Lambda 表达式

c - 运行一个程序,我将文件名传递给 Main

c - 在 C 中将数组作为参数传递

c# - linq to list<object> 并获取每行的值

C# "Constant Objects"用作默认参数

c - MSVC : "too few arguments in function call" when omitting optional parameter

clojure - 改进循环递归

java - 如何为可选参数创建哈希码和等于

clojure - Clojure 编程在 Kindle 上的表现如何?