使用as绑定解构可变函数:不可能吗?

4

直到现在,我一直认为你可以在 let 绑定中做的任何事情,都可以在 defn 表格的参数向量中完成。

然而,我刚刚注意到这个问题——如果我使用 let 绑定,它会起作用:

(let [[x & more :as full-list] (range 10)]
  (println "x:" x) 
  (println "more:" more) 
  (println "full list:" full-list))

; x: 0 
; more: (1 2 3 4 5 6 7 8 9) 
; full list: (0 1 2 3 4 5 6 7 8 9)

但是如果我尝试将它转化为函数,就会出现异常:

(defn foo [x & more :as full-list]
  (println "x:" x) 
  (println "more:" more) 
  (println "full list:" full-list))

; CompilerException java.lang.RuntimeException: Unexpected parameter, compiling:(/tmp/form-init615613631940782255.clj:1:1)

值得注意的是,这个可以正常工作:

(defn foo [[x & more :as full-list]]
  (println "x:" x) 
  (println "more:" more) 
  (println "full list:" full-list))

但是我必须将参数作为集合传递,即(foo [1 2 3])
是否可以定义一个接受可变数量参数的函数,并将整个参数组绑定到本地变量,而不特别使用let绑定?让我感到奇怪的是,你不能只做(defn foo [x & more :as full-list] ...。这个方法不起作用(或者不应该起作用)的原因是什么?

+1 我最近也遇到了这个问题,感到相当沮丧。 - arrdem
1个回答

5
如果想要一个可变数量的参数,你漏掉了一个 & :
(defn foo [& [x & more :as full-list]]
  (println "x:" x) 
  (println "more:" more) 
  (println "full list:" full-list))

Clojure的参数定义只有一种特殊情况,即使用&字符表示变长参数。其余的都是普通的命名参数。

现在,每个简单参数都可以使用映射或列表语法进行解构。例如:

(defn foo [ x y ] ...)

可以像这样解构:

(defn foo [[x1 x2 & x-more :as x] {:keys [y1 y2 y3]}] ...)

我们认为第一个参数应该是至少有两个元素的列表,第二个参数应该是一个有一些键的映射。需要注意的是,它仍然是一个带有两个参数的函数,并且Clojure不会强制 x 实际上至少有两个元素。如果 x 是一个空列表,x1 和 x2 将为 nil。
回到你的问题,如果你看看我的答案,你会发现我的函数没有必填参数,只有可变数量的参数,而你所拥有的函数有一个必填参数和一个可变数量的参数。我所做的就是对变量参数进行解构。

可以了 - 谢谢!有趣的是,你只有在想使用 :as 形式时才需要添加第一个 &... (defn foo [x & more] ... 可以正常工作,但一旦添加了 :as 部分,就必须添加初始的 & 以明确函数是可变参数的。我不知道为什么会这样? - Dave Yarwood
我已经更新了响应,并加入了更详细的解释。如果现在更容易理解,请告诉我。 - DanLebrero
这使得它更加清晰,谢谢 - 我刚刚有了一个小的“顿悟”时刻。我想let绑定工作方式与defn绑定工作方式之间的一个关键区别是,let期望至少将一个东西绑定到本地变量,而函数可以不带参数。有了这个差异,更有意义的是在绑定的前端要求额外的&,以指示函数可能需要0个参数。 - Dave Yarwood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接