Hacker News new | past | comments | ask | show | jobs | submit login
How I learned to stop worrying and love macros (zdimension.fr)
140 points by magnio on Aug 19, 2022 | hide | past | favorite | 52 comments



> "macros" are probably the first thing you're taught to be afraid of (second only to "moving a picture in a Word document"),

Next time, trigger warning please! I still have PTSD from that time I tried to text flow around an image correctly back in ‘08 on deadline. I just try to think of God and country instead…


> Here, there's a conflict between the lexical scope of the function (main) and the macro (SWAP).

It might interest to some to know that GCC has a macro called __COUNTER__ (apparently also implemented by clang and MS), which auto-increments its value. You can then create something like a gensym to avoid conflict.

Here's an implementation of the "defer" keyword in C++:

    template <typename F>
    struct privDefer {
     F f;
     privDefer(F f) : f(f) {}
     ~privDefer() { f(); }
    };

    template <typename F>
    privDefer<F> defer_func(F f) {
     return privDefer<F>(f);
    }

    #define DEFER_1(x, y) x##y
    #define DEFER_2(x, y) DEFER_1(x, y)
    #define DEFER_3(x)    DEFER_2(x, __COUNTER__)
    #define defer(code)   auto DEFER_3(_defer_) =     defer_func([&](){code;})
It was written by gingerBill, author of the Odin programming language.


TIL, I'd never have thought something line gensyn could be possible using C macros (though this is an extension). This is both amazing and horrible


I would advice against using __COUNTER__ or macros based on that in header files. It's an ODR-violation footgun.


My favourite example of macro usage:

https://code.jsoftware.com/wiki/Essays/Incunabulum


The post starts slow, but is worth reading for when they get to embedding C and VB Script directly in rust programs.


X-macros in C [1] are great for doing compile-time code generation from tabular data. Especially for things like pinouts and their respective capabilities on microcontrollers, I haven't seen any other language give such a succinct, easily maintainable way of interacting and representing data such that it can be used for full function generation that an IDE can autocomplete names, types etc.

Sure there are other "newer" ways but they invariably involve so much boilerplate that you might as well just write all the code by hand.

1. https://en.wikipedia.org/wiki/X_Macro


One nice feature of Rust macros that isn't usually trumpeted is that it allows varargs to be punted out of the core language. Consider:

- Varargs are, generally speaking, a pain and a wart, especially in statically typed languages

- Printing stuff is horribly unergonomic without varargs (C++'s iostream << is arguably a get-out here, but is widely hated),

- There are no other compelling 'mainstream' uses for varargs afaik

By having print as a macro, Rust can swerve this particular tarpit. Not suggesting that it's the reason for macroising print, but it's a welcome bonus nevertheless.


They are only a wart, if the typesystem is not powerful enough (such as Rust's typesystem). With dependent types, this is a non issue. In fact, you can implement something like typesafe "printf" as a regular user-defined function in a language like Idris and to some degree even Scala.

See e.g.: https://gist.github.com/chrisdone/672efcd784528b7d0b7e17ad9c...


Everything which needs the format-arbitrary-parameters feature in Rust is built out of macros, so yes it's the reason that println! is a macro. Even with no_std where you don't have an Allocator, and so you can't use format! to get Strings (because there aren't any Strings) you still have mechanisms built out of macros, so you could e.g. write six u16s and a f32 to the serial port on your embedded controller (you could write a &str, but since you can't allocate Strings it'll have to be some pre-defined value like "Error" or "OK").

These functions are especially tricky because we want to have a variable number of arguments with arbitrary types. We want to be able to print two bytes, a string, an array, and six different arbitrary data structures we decided were important.

There are lots more applications for function(a, b, c, d, ...) where a, b, c and so on are all the same type. It might be nice to define min() and max() over these parameters for example. But cases like print-format where it's sane to choose unrelated types are rarer. Languages where I can write min("Cheese", 4, tmpfile, -Inf) have lots of other problems that outweigh the potential usefulness of such constructions.

I expect that if somebody can write a sound but ergonomic varargs story for safe Rust it'll get introduced, even if they can't solve print-format it's still useful as I showed above.


Varargs with the same type is just a list. In many languages that could require an extra allocation, but in Rust that can just be a slice or maybe a an array with a const generic for it's size if you want to pass ownership.

Varargs here would really just be a minor ergonomics improvement.

Heterogeneous varargs would be more useful, but would require something like variadic generics. (which have been proposed before)


Macros totally fail at keeping varargs out of core because of generics. Try writing emplace_back in Rust, or anything else that would need to generically do placement new.


This is hilarious in addition to being technically accurate and insightful.


Thanks! Glad you liked it!


If anything, this post has only succeeded in making me even more anxious about all languages that aren't built on s-expressions...


S expressions without types make me nervous


Me too actually[1], and I'd really like trying to work with something like typed racket[2] some day.

[1] I say this as a big fan of clojure

[2] https://docs.racket-lang.org/ts-reference/index.html


The best way to get rid of that feeling is to work with them. Why not learn Common Lisp?


And Common Lisp actually allows adding type declarations[1], which can make code much more performant!

Also, if you really, really want types, as in Haskell-like types... there's Coalton[2], a language that's embedded in Common Lisp (CL is that powerful it allows things like this) with a ML type system and more "modern" idioms for people used to functional programming.

[1] https://lispcookbook.github.io/cl-cookbook/type.html

[2] https://coalton-lang.github.io/


Because it is Lisp, you will be working solo anyway, and NIH-ing all your dependencies. So it is all good!


If it's all good, sounds like that's a reason to learn it. ;) But I have to push back some -- I mean you can work alone and build your own castles from scratch if that's your thing (I totally understand that appeal), as in any language, but you don't have to. There are libraries, some quite good, here's an incomplete collection: https://github.com/CodyReichert/awesome-cl It's also easy to interface with C libraries or Java libraries, there are options to interface with C++ and Python. There are also communal projects with more than one active developer if you wanted to work on such group things, and some companies here and there.


All languages are built on s-expressions. (Aka ASTs) Lisp is just the only one proud of the truth.


While that may be technically true, it's not really true in a way that is relevant to what I meant by my comment (ie.: the syntax maps directly to the AST, or s-expressions are the syntax through which one expresses the program).


It's about time someone write an article "macro considered harmful" [1]. Any taker?

[1] https://news.ycombinator.com/item?id=8698594


JavaScript looks like Rust next to C macros.


That’s harsh


Great post, thanks. Finally makes me want to learn Lisp


Glad you liked it! If you want to learn Lisp, I'd recommend something like Racket (implementation of Scheme, a dialect of Lisp), it's a "batteries included" Lisp that should be easier for a beginner to learn. The built-in code editor (DrRacket) is, eh, good enough.


Teach me how to stop worrying


Try to visualize yourself lying in bed 5 minutes before your death. Imagine you know you're dying, set a timer, lay down, and really try to put yourself in your own shoes.

Let the panic set in, don't try to change the subject. Imagine as the seconds slip by that you struggle to reflect on all the parts of your life. 5 minutes can seem like a long time, but when it is your last few seconds they vanish quickly.

Really be in that moment. Take it seriously. Embrace the terror.

When the timer goes off none of the problems in your life will seem one-tenth as serious as before.


Apart from wanting to delete all hentai and open the door so that my nice landlord doesn’t have to deal with a two weeks corpse, I only feel free and not particularly terrified. Also miserable because nothing made enough sense, as usual. Of course in a real situation a pure biological drive would kick in, but not in this thought experiment. The last time I’ve got the most popular disease of the decade, I actually thought I may end up dead and did the two things and felt as above. Thing is, people may have very different views on life and reasons to worry about it. I for one seem to not really worry about the end (still to be explored in case of something long like cancer), probably because I don’t see it as a period of existence. The period of existence and the fuck is with it is what worries me.

Disclaimer: I’m not in debt, pain, legal/criminal trouble, not relatively poor and not suicidal.


Or just remember that, on the timescale of the universe, 50 years is almost as brief as five minutes.

Soon you'll be dead, and everyone you love, everyone you know. And everyone who knows them, and everyone who knows them, and so on on down the line. And not just dead. Forgotten. Completely.

Does that change your priorities in life at all?

I take two things away from this exercise.

1. Try to be happy in the moment.

2. Try to build things that will outlast you. Nobody remembers the names of the architects of the Roman aqueducts, but people in Italy still benefit from their work today.


1 is fine, but do take into account the future sometimes, and be ready for change. As a human you have the ability to do both. Suppose medical technology advances so that within 50 years, you no longer have to worry about dying from old age and associated issues, only random factors like getting hit by a bus (which could just as well happen tomorrow). In effect, many of those still living will no longer soon be dead. If you had an oracle from the future to confirm a particular date when this happens, does that change your priorities in life at all?

2 is most done reliably by having children. Nevertheless, Vitruvius?


Why presume you could afford said technology?


Not presuming that might focus your current priorities in life if you think the tech is reasonably likely to come about before it's too late for you and be expensive for a long time, like you may want to focus more on getting a higher paying job, more frequently asking for raises, or switching companies to make more money, better controlling your spending and trying to save/invest more wisely...

There's a moral argument I could make that suggests even with high costs lots of people would get it anyway out of a sense of civilizational fairness, or a history-of-technology argument I could make about tech getting cheaper over time, but I think the argument I like best and that works on its own is just pointing out that there's a huge economic incentive to government and society more broadly to provide such technology to everyone even for free (from their individual perspective at time of treatment).

On the production side is the incentive of having healthy citizens who don't have to stop working on account of age. There's something like 16% of the population just in the US who are older than 65, something like 20% of them are still working, and some of those not working would be more than happy to go back to working but can't because of age-related issues (the final one being death). Adding some of them back to the economy would be a nice boost worth paying a lot for.

From the spending side, currently governments subsidize medical treatment for lots of people, sometimes even for everyone and even for brand new technologies like mRNA vaccines. There's a huge network of so-called insurance providers which also play into this; some things are fully covered by a plan while others are subject to more complicated rules. The dynamics of how money actually flows around and who really pays for what are complicated but society as a whole is currently paying a lot of money for the benefit of individuals. So we can expect longevity treatments to be subsidized and affordable for some people to some extent because that's the case with every other medical treatment. We can expect it to be a large number of people, trending towards everyone, and heavily subsidized if not otherwise affordable, trending towards free (from the individual perspective), because of the nature of the 'disease' (age affects everyone, like a global pandemic) and the consequences of treating it or not. We already know the consequences of not treating aging, and a lot about their expenses. So much is spent just on end-of-life care (for US Medicare alone, 13%-25% of its budget is spent during recipients' last year of life) let alone all the age-related issues leading up to that. The government or society more broadly spending money on individuals now for otherwise unaffordable rejuvenation tech can make a lot of sense when the alternative is spending a lot more money on them not too much later for unaffordable end-of-life care.


This made me think of AstronoGeek's video "Quel est le sens de la vie ?" (what is the meaning of life?) (French only) [1]

Enjoy your irrelevance! :-)

[1] https://www.youtube.com/watch?v=uWRONHKcbu8


That was beautiful.


Only allow ones that are hygienic. Maybe also have a tool like DrRacket which will show you stage by stage what macros expand to (including features like not expanding common ones like "define" in Racket or "println!" in Rust). I think the latter in particular is important since debugging what code you're generating is very important.


For Emacs Lisp users M-x macrostep-expand from the macrostep package is a cool tool that allows for inline expansion/collapse of macros, step by step if there are multiple levels of macros.

There is also M-x pp-macroexpand-last-sexp that comes with Emacs. And the macroexpand function itself of course.


Does Elisp have hygienic macros (without things like gensym)?


It’s ok to worry, it just means you care. Find people who will worry and care with you about the same things you do and it will be feel better. Partially because you’ll find more important things to worry about, and partially because talking about it will help you cope!


Little technical nitpick: the code blocks on the linked site are unreadable without JS enabled. They have a white background & light grey text as default.


Sorry about that! I'm still trying to find a way to get good support for dark mode with a dynamic dark mode switcher, and it seems that the current one I'm using (from V8 labs) behaves... erratically when JS isn't enabled. I'm working on it!


The post consists of two parts: educational (what macros are) and entertaining (use and abuse of macros in Rust).

I recommend both, and the punch... line made me LOL IRL.


This piece is fantastic and makes me want to learn Rust !


Thank you so much! Glad you liked it! If you want to learn Rust, check out the book (https://doc.rust-lang.org/book/) and Rustlings (https://github.com/rust-lang/rustlings) (runnable in the browser), both are amazing resources


Thanks for the links! I unfortunately have very little time for learning new tools but very insightful article!

I am sure you're trying to make friends in the rust community with the "on error resume next" thing!


Macros and (nigh) mandatory code generation and boilerplate are proof your language sucks. It might go fast, but it sucks.


Doing some calculations at compile time is useful. What sucks is when the language for expressing this is difficult to think about and interacts poorly with normal code.


It might prove that the language has decided not to have too many language features.


I dont know, I see macros and code generators as the one tool to battle boilerplate. Functions often have boilerplate syntax rules. Macros do not :^)


What sucks much more is, when one cannot extend the language properly and it needs a language commitee to change anything. Or when the way to extend it is so mistake and error prone, that one cannot get it right, needs many eyes viewing it for a few years, before it becomes probably correct.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: