Clojure嵌套宏

时间:2014-06-06 21:57:17

标签: macros clojure nested

是否可以宏返回宏?我想简化代码最大化,我可以使用返回函数的宏来完成此操作。但是,它的开销太大而且速度太慢。为了使代码更具可读性,我没有使用类型提示,但即使使用它们,我的代码也慢了约5倍。

我的英语不是很精确,所以我写下我拥有的和我想要的东西。

我有这个......

(defmacro series [java-array]
  `(fn 
     ([i#]
      (aget ~java-array i#))
     ([start# stop#]
      (let [limit# (unchecked-subtract-int stop# start#)
            result# (double-array limit#)]
       (loop [i# 0]
        (if (< i# limit#)
          (let [r# (double (aget ~java-array i#))]
            (aset result# i# r#)
            (recur (inc i#)))
          result#))))))

我想要这样的东西......

(defmacro series [java-array]
  `(defmacro blabla 
     ([i#]
      `(aget ~~java-array i#))
     ([start# stop#]
      `(let [limit# (unchecked-subtract-int stop# start#)
             result# (double-array limit#)]
         (loop [i# 0]
           (if (< i# limit#)
             (let [r# (double (aget ~~java-array i#))]
               (aset result# i# r#)
               (recur (inc i#)))
             result#))))))

但是当我称之为......

Wrong number of args (1) passed to: blabla

一个更简单的例子。

我有很多java数组。我不想用aget。我希望宏扩展到(aget array-name i)。我写了一个扩展到(fn [n] (aget array-name i))的宏,但这是不必要的开销。

(defmacro series [arr]
  `(fn [i#]
     (aget (longs ~arr) (int i#))))

我现在声明系列,例如“date”,并以这种方式调用它(date i),它将返回数组的“i”元素。

1 个答案:

答案 0 :(得分:4)

我认为您正在寻找的是一种在函数中声明本地宏的方法。 clojure.tools.macro包具有macrolet形式,我认为您应该能够为本地数组查找创建宏。

这是我放在一起的一个小例子:

; project.clj
(defproject testproject "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.5.0"]
                 [org.clojure/tools.macro "0.1.2"]])

; src/testproject/core.clj
(ns testproject.core
  (:require [clojure.tools.macro :refer [macrolet]]))

(defn my-test []
  (let [n 1000
        arr (int-array n (range 10))]
    (macrolet [(lookup [i] (list `aget 'arr i))]
      (loop [i 0, acc 0]
        (if (< i n)
          (recur (inc i) (+ acc (lookup i)))
          acc)))))

在示例中,我使用macrolet来声明一个名为lookup的宏,它会导致(lookup 5)扩展为(lookup arr 5),这就是我认为你的意思寻找。

请注意在本地宏定义中引用arr时必须小心。如果您刚刚将宏声明为`(aget arr ~i),那么它会尝试查找一个完全限定的符号arr,该符号不存在。您也可以将宏声明为`(aget ~'arr ~i),但我觉得(list `aget 'arr i)看起来好多了。

现在您可以更进一步,使用defmacro创建一个内部包含macrolet的宏,您可以使用它来简化使用本地查找宏的数组声明。 我们将此作为练习留给读者。

由于宏是如此微妙,并且在宏中嵌套宏只会使事情变得更糟,我决定只举一个例子并让你弄清楚它是如何工作的可能会更有帮助:

(defmacro lookup-binding [[fn-sym value] & body]
  (let [array-sym (symbol (str fn-sym "-raw"))]
    `(let [~array-sym ~value]
       (macrolet [(~fn-sym [i#] (list aget '~array-sym i#))]
         ~@body))))

(defn my-test2 []
  (let [n 1000]
    (lookup-binding [arr (int-array n (range 10))]
      (loop [i 0, acc 0]
        (if (< i n)
          (recur (inc i) (+ acc (arr i)))
          acc)))))

免责声明: 我只是想表明这是可能的,而不是它是一个好主意。我个人认为所有这些额外的复杂性都值得避免aget。我建议只使用aget,因为它只需要几个额外的字符,它会使您的代码更清晰/可读。