Ribelo
Ribelo

Reputation: 205

Clojure Nested Macro

Is it possible to macro returned macro? I would like to simplify the code maximum, and I can do this using a macro that returns the function. However, it is too much overhead and it is too slow. To keep the code more readable, I did not use type hints, but even with them my code is ~ 5x slower.

My English is not very precise, so I write what I have and what I wanted to have.

I have this...

(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#))))))

I want something like this...

(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#))))))

But when I call this...

Wrong number of args (1) passed to: blabla

A simpler example.

I have a lot of java arrays. I do not want to use aget. I want macro to expand to (aget array-name i). I write a macro that expand to (fn [n] (aget array-name i)), but this is unnecessary overhead.

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

I now declare the series, such as "date", and call it in this way (date i), which will return me "i" element of the array.

Upvotes: 3

Views: 1288

Answers (1)

DaoWen
DaoWen

Reputation: 33019

I think what you're looking for is a way to declare local macros within functions. The clojure.tools.macro package has a macrolet form, which I think you should be able to wrangle into creating a macro for local array lookups.

Here's a little example I put together:

; 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)))))

In the example I use macrolet to declare a macro called lookup that will cause (lookup 5) to expand into (lookup arr 5), which is what I think you're looking for.

Notice how you have to be careful when referencing arr from within the local macro definition. If you just declared the macro as`(aget arr ~i), then it tries to find a fully-qualified symbol arr, which doesn't exist. You could alternatively declare the macro as `(aget ~'arr ~i), but I felt that (list `aget 'arr i) looked a lot nicer.

Now you can take it one step further and use defmacro to create a macro that includes macrolet inside, which you can use to simplify the declaration of arrays with a local lookup macro. We leave this as an exercise for the reader.

Since macros are so subtle, and nesting macros in macros only makes things worse, I decided it would probably be more helpful to just give an example and let you figure out how it works:

(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)))))

Disclaimer: I'm only trying to show that this is possible, not that it's a good idea. I personally don't think all this extra complexity is worth avoiding aget. I'd suggest just using aget since it's only a few extra characters, and it will make your code more clear/readable.

Upvotes: 4

Related Questions