【问题标题】:How to write a macro (average n1 n2 n3...) to compute the average of the numbers如何编写宏(平均 n1 n2 n3 ...)来计算数字的平均值
【发布时间】:2016-03-08 22:52:56
【问题描述】:

我想使用 defmacro 来计算数字列表的平均值。它应该像这样运行:

user=> (avg 1 2 3 4 5 6)
3.5

我的代码是:

(defmacro ave [& number]
   `(float `(/ `(reduce + ~number) `(count ~number))))

但我得到了一个错误:

**ClassCastException clojure.lang.Cons cannot be cast to java.lang.Number  clojure.lang.RT.floatCast (RT.java:1262)**

我该如何解决?谢谢

【问题讨论】:

  • 请注意,这不是使用宏的真实案例,这是一个关于如何编写宏的好问题。如果您以后遇到此答案,请不要编写宏,除非必须这样做。

标签: clojure macros


【解决方案1】:

正如 amalloy 所建议的,最好的解决方案是函数:

(ns clj.core
  (:require [clojure.string :as str] )
  (:use tupelo.core))

(defn avg
  "Compute the average of 1 or more vals."
  [& values]
  (when (zero? (count values)) 
    (throw (IllegalArgumentException. "avg: error - at least 1 value required")))
  (let [total   (apply + values)
        result  (double (/ total (count values))) ]
    result))

这是测试:

(ns tst.clj.core
  (:use clj.core 
        clojure.test 
        tupelo.core))

(deftest t-avg
  (is (thrown? IllegalArgumentException (avg)))
  (is (= 1.0 (avg 1)))
  (is (= 1.5 (avg 1 2)))
  (is (= 2.0 (avg 1 2 3)))
  (is (= 2.5 (avg 1 2 3 4))))

请注意,该函数比宏更强大,因为宏不能作为参数传递给高阶函数(例如过滤器、映射等)。

【讨论】:

    【解决方案2】:

    您应该使用一种语法引用来包装整个表达式,并为您的数字取消引用拼接作为列表参数。除此之外,由于reducecount 接受列表作为参数,我使用list 来包装它们。所以,试试:

    (defmacro ave [& number] 
      `(float(/ (reduce + (list ~@number)) (count (list ~@number)))))
    

    REPL 中的结果:

    user=> (ave 1 2 3 4 5 6)
    3.5
    

    更新:根据amalloy在cmets中指出的错误,这是我的第二个版本:

    (defmacro ave [& number] 
      `(let [ns# (list ~@number)] 
         (float (/ (reduce + ns#) (count ns#)))))
    

    我在 REPL 中的测试:

    user=> (ave (do (println "Doing a lot of work...") 1) 2 3 4 5 6)
    Doing a lot of work...
    3.5
    

    【讨论】:

    • 除了使用宏是一个糟糕的问题之外,您的宏实现还有一个严重的问题,即它会重复评估number:例如像(ave (do (println "Doing a lot of work...") 1) 2 3 4 5 6) 一样尝试.
    • @amalloy:你是对的。我是一个有宏的大新手。我想我应该使用 gensym 本地绑定。让我试着修复它。
    • 非常感谢!它确实有效,但是如果我将参数更改为向量,例如 ave [1 2 3 4 5 6],然后将代码更改为 (defmacro ave [vector] (float (/ reduce + (~@vector)) (count (~@ vector)))) 会怎样。但是我收到一个错误,例如传递错误数量的参数。我不完全理解为什么我们将列表放在 ~@ 之前。谢谢!
    • @XiufenXu 这就是为什么它根本不适合作为宏。它应该只是一个接受事物列表的函数。
    • @amalloy 我同意你的看法。我也是宏和 gensym 的新手。我应该用宏完成一项任务。我已经解决了将参数作为向量的问题。谢谢大家!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-14
    • 2012-06-19
    • 2017-02-06
    • 2013-10-25
    • 1970-01-01
    相关资源
    最近更新 更多