如何将可变参数宏传递给函数?

时间:2016-11-10 21:04:36

标签: clojure

我正在尝试学习Clojure并且遇到宏问题。我有一个简单的宏:

(defmacro add [& x] `(apply + (list ~@x)))

然后我有一个可以调用多个操作的函数(加,减,乘和除)。我从stdin得到了这些,但为了简单起见,我创建了一个包含数字列表的变量:

(defn do-op
  [op]
  (def numbers (list 1 2 3))
  (apply op numbers))

为了使用适当的操作调用do-op,我需要将宏传递给匿名函数中的do-op

(do-op #(add %&))

当我使用从stdin传递的参数运行此代码时,我得到一个令人困惑的错误:

Cannot cast clojure.lang.ChunkedCons to java.lang.Number

当我在REPL中执行此操作时,我得到:

Cannot cast clojure.lang.PersistentList to java.lang.Number

我认为这与我对如何处理这些可变参数的理解缺乏了解有关但我完全被难倒了。我错过了什么?

3 个答案:

答案 0 :(得分:1)

您永远不会将宏传递为函数,因为在编译代码并准备运行后它不存在。

当你有;

(do-op #(add %&))                              ; which is shorthand for
(do-op (fn [& rest] (add rest)))               ; then you expand the macro
(do-op (fn [& rest] (apply + (list (UNQUOTE-SPLICE 'rest))))   ; how do you unsplice the symbol rest to be separate elements?

简短的回答是rest永远不会被解除拼写,因此您使用宏错误。但是,您似乎只是为+创建别名,可以通过创建函数或仅对原始函数进行绑定来完成:

(defn add [& args] (apply + args)) ; making a function that calls + with its arguments
(def add +)                        ; or just make a binding pointing to the same function as + points to
(do-op add) ; ==> 6

你应该继续使用函数作为抽象。每当你需要编写一些非常冗长的东西时,由于过早的评估而无法使它成为一个函数,那么你就有了宏的候选者。然后它用一种较短的方式来编写它,用冗长的冗长方式编写它,而不知道变量符号背后的实际数据类型,因为编译器在扩展宏时不会知道它们是什么。宏也会扩展一次,而具有宏的函数可能会多次执行。

编辑

在您的pastebin代码中,我可以确认您永远不需要宏:

(cond
  (= 1 option) (do-op #(add %&))
  ...)

应该只是

(cond
  (= 1 option) (do-op +)
  ...)

无需为此包装或制作宏。按原样传递函数,它将执行您想要的操作。

答案 1 :(得分:0)

宏在编译时运行并转换代码数据,而不是运行时数据。

根本没有办法将一个表格一般地应用于一个表单(如符号numbers),因为它需要首先进行评估,这只能在运行时(编译时间之后)发生。

这是完整的答案。希望它可以帮助您理解Clojure中的宏。

答案 2 :(得分:-1)

如果我理解你在想什么,你应该这样做:

(ns clj.core
  (:require 
    [tupelo.core :as t] 
  ))
(t/refer-tupelo)

(defn add [& x] 
  (apply + x))

(def numbers [1 2 3] )

(defn do-op
  [op]
  (apply op numbers))

(spyx (do-op add))

(defn do-op-2
  [op vals]
  (apply op vals))

(spyx (do-op-2 add numbers))

获得结果:

> lein run
(do-op add) => 6
(do-op-2 add numbers) => 6

通常我们会尽量避免让函数访问像numbers这样的全局值。使用do-op-2,我们将数字向量传递给函数。

注意:要让(spyx ...)工作,您必须包含

项目.clj
:dependencies [
  [tupelo "0.9.9"]

更新

当然,你真的不需要do-op,你可以这样做:

(defn add-all [nums-vec] 
  (apply + nums-vec))
(spyx (add-all numbers))

;=> (add-all numbers) => 6
相关问题