Fork me on GitHub
#clojure
<
2023-01-08
>
Drew Verlee17:01:01

When should you use a macro? Recently, i realized the creation of a macro starts with thinking about how humans think, specifically how they think about the shape of things. Some example shapes and what they mean to me. (1 2 3 ): list ->: left to right <-: right to left So a macro can be a tool for making how something looks mirror how it behaves. For example, the core async paints a picture of a queue and how to enqueue and dequeue. Another example is the when macro, which grammatically visually trims down the flow to one branch. What macros have you seen that instantly helped you understand how something behaved? Which ones betrayed you, or simply did nothing but add overhead? What do you when a macro isn't enough? I think the next step is you have to write a program to explain your program/function/algorithm. What structures do you find aren't visually represented in clojure that you have to reach for on a regular basis? Would any of them be good candidates for macros? Or if you disagree with the premise, could you state why?

p-himik17:01:36

> So a macro is a tool for making how something looks mirror how it behaves. Macro is something that you can use in some cases when a function isn't enough. Whether it has anything to do with the looks, is up to the author.

Drew Verlee17:01:32

Good point, i meant to say that it can be , not imply it only is.

Ed17:01:38

You only write a macro because you care about the syntax of the code you're writing.

practicalli-johnny17:01:13

I've always considered macros as a tool to extend the Clojure language over using what is already available in the Clojure language, so requires much hammock time. A macro would be well documented and have a name that readily conveys its purpose. Ideally there would be significant value gained for a macro across numerous projects, otherwise a macro may become another piece of technical debt in a project. As Clojure is expressed as a data structure, then the original premise of this thread sounds a useful technique to think about the design of some macros

teodorlu19:01:01

I read your message as "macros should be used when they reduce cognitive load", which I agree with!

jpmonettas22:01:25

I think one needs to be careful when creating a definition for macros in general since it can limit how you use them. I think there are many aspects to them : • the syntax extension aspect (like ->>, core.async/go, etc) • the evaluation control aspect (like cond, since you can't do it with a function) • the code rewriting aspect (macros you use to wrap general pieces of code to rewrite it, for things like optimizations, execution tracing, etc) • the pre-processor aspect (at compilation time, by looking at the environment decide what to compile or include in the compilation, pretty useful in clojurescript) and probably more...

👍 6
jpmonettas22:01:54

I think it is important to have all those aspects in mind, because lisp macros allow all of them, while other macro systems just focus on syntax extension

Drew Verlee23:01:30

Thanks for sharing @U0739PUFQ Can you give an example of something at pre-processing which isn't possible at run time? I feel like i understand that you can use a macro for this, but what i don't understand is why you have to. E.g this macro will save the build time when it compiles

(defmacro build-time []
  (str (java.util.Date.)))
But you could also just write that time to a file and have your app read the file right? As in, it's maybe a perf improvement. That is a big deal of course, just making sure i understand the value proposition. The code evaluation aspect you raise is important, i think that definitively needs to reflect the shape / visual aspect of the macro to ideally, otherwise i'm not sure users will intuitively understand it's happening.

jpmonettas00:01:28

I remember using the "pre-processing" aspect mainly on ClojureScript, for doing things like expanding to different code if some shell environment var is on "dev" vs "prod". So the code that will end up in your compiled js can be customized by changing env vars, or jvm params. Kind of using it like #ifdef DEV ... #endif in C. Sorry I currently don't have a concrete example at hand. In Clojure this aspect is not that useful since most of the time we deliver .clj files and they are macroexpanded and compiled on the target machine, but maybe can be useful with AOT compiling?

👍 2
skylize13:01:11

> when a function isn't enough. -@U2FRKM4TW What is your definition of "isn't enough"? It can certainly be a fun exercise to think of ways to use only functions for things one would generally think requires a macro.

(defn if' [pred if-true else]
  ({true if-true false else} (boolean pred)))

(if' true "😸" "👿")        ; => 😸
(if' false "😸" "👿")       ; => 👿
(if' :a-keyword "😸" "👿")  ; => 😸
(if' nil "😸" "👿")         ; => 👿

p-himik13:01:55

In your version, both branches will be computed - there'll be no short-circuit. Same for or and and and anything like it.

skylize13:01:26

Fair point. A macro alone wouldn't get you around that though, would it? Special forms are .... well, special.

jpmonettas13:01:06

@U90R0EPHA macros are good for that, I added at my second point : • the evaluation control aspect (like cond, since you can't do it with a function) and , when and cond are all examples of that

jpmonettas13:01:27

that is because no args get evaluated when calling the macro, so the macro can decide on the order of execution or even remove all execution (like in comment)

skylize13:01:16

I think when is about the worst example of "can't do" you could come up with. That is aesthetics through and through: just if with the else branch hidden from view.

jpmonettas13:01:51

I mean, when can't be implmented as a function

jpmonettas13:01:37

the thing with functions is that all arguments expressions get evaluated before calling the function, so no chance for functions to control evaluation

skylize14:01:15

> when can't be implmented as a function Ummm..... How is this functionally (or even visually) different from the when macro?

(defn when' [pred x] (if pred x nil))
(when' false "😸") ; => nil

jpmonettas14:01:16

well if you do :

(when' false (println "should't be here"))

or 

(when' false (expensive-computation))

jpmonettas14:01:37

both won't do as you expect

skylize14:01:43

I learned something. 😄

😀 2
Cora (she/her)19:01:54

I usually think of macros as a tool to change the evaluation rules of code.

Cora (she/her)19:01:44

everything else feels secondary, to me