The C# macro system - anyone using both Clojure & C#?

Well in Lisp-land, there’s an adage that says you should avoid using macros as much as you can, precisely because they are hard to compose, since they don’t follow any consistent rule of evaluation.

As a user of a macro, unlike with a function, you never know what could happen, you have to understand the semantics of each macro you’re using, and they can all differ in fundamental ways, so there’s no guaranteed semantics you can rely on.

I’d say in general, you expect that a macro will not evaluate its arguments at compile-time, because most macros don’t, they simply rewrite one piece of code into another or generate code from the provided code.

So if you have a macro that performs compile-time eval, as a macro user, that’s a little surprising, and the macro would need to document that.

I think that’s why it’s more rare. When you start mixing compile and runtime in ways that aren’t interchangeable, things can get messy.

So in my prior example, what if someone did:

(gen-constant-fn
  (fetch-name-from-prod-db "foobar")
  (compute-suffix-from-remote-api "car"))

With most macros, you’d expect that the code you gave the macro will be evaluated at runtime, and that the macro will simply expand this code into a longer form or a different form. Here if it ran at compile time it would fail, my desktop doesn’t have access to prod-db and remote-api, my build server also doesn’t.

So as a user, I need to understand, oh, either I provide a value to this macro, or I can provide computations, but they must be runnable at compile-time.

Some macros definitely do this, sometimes you need too, that’s the point, this is the behavior you want, but I’m just pointing out it’s all these kind of “gotchas” in that each macro can have very different set of what can or can’t be done and how will they do things and how do you compose that with other macros or functions?

I don’t think there’s a solution, but I have long wondered about something like you suggested, like what if the user could override eval ordering of the macro, like:

(some-macro (compile-eval (compute-name T)) (compile-eval (+ 1 2 3)))

Where compile-eval would be a special form to the compiler that says evaluate those before the macroexpansion.

Edit:

Well, actually there is a kind of way to do that in Clojure, but it’s not well documented, and while it’s survived many years, I hear it’s not something the core team is fund of, and they might one day remove or modify it in breaking ways, but there is a reader eval that allows you to evaluate things at read-time, which is before macroexpansion:

(concat-id #=(eval (compute-name t)) Suffix)

This would evaluate compute-name before concat-id, it’s considered bad practice, not sure why.