Skip to content

The C3 Blog

An evolution of macros for C

Originally from: https://dev.to/lerno/an-evolution-of-macros-for-c-59b5

(This text was previously published on Medium)

I’ve been trying for a long time to think up a good macro system that could replace or extend the C preprocessor and yet be as easy and approachable.

There has recently been a lot of interesting work on alternatives to C and C++, and consequently those languages have tried to fix both macros and templates. In some languages, like Rust, there’s a very rich set of tools to extend the language, the inspiration here has clearly come from languages like LISP where macros have been a way to expand the language itself. There are other approaches though: both Zig and Jai uses compile time execution of the language to avoid any specialized macro syntax. Zig is notable for making this a large part of the language.

In many ways those macro systems are to C’s as a strong typed language is to C’s weakly typed one. The increased type and error control also means added complexity in defining macros. If we write a language that overall is much stricter than C, then this is both fine and necessary. But for C, do we really want to constrict ourselves?

What would a sort of “incremental” improvement for a macro system for C look like? Can we make a minimal extension that doesn’t feel like we’re making a whole new language?

Let us make an attempt!


Step 1, we could make multiline #defines more readable by adding { } to implicitly allow row breaks:

#define foo(a, b) \
  int x = run_foo(a, b); \
  if (x > 0) printf("We got foo!\n");

// => 

#define foo(a, b) {
  int x = run_foo(a, b);
  if (x > 0) printf("We got foo!\n");
}

Step 2, accidentally shadowing other variables is bad. Let’s create unique variable names on demand by a prefix of $ inside a #define:

#define foo(a, b) {
  int $x = run_foo(a, b);
  if ($x > 0) printf("We got foo!\n")
}

foo(1, 2); // $x expands to __foo_x_1
foo(100, 20); // $x expands to __foo_x_2 (increment by one for each time expanded)

Step 3, __typeof__ is needed to do a lot of nice macros, let’s lift it to a sanctioned typeof function. We can now rewrite GCC’s MAX:

#define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })

#define max(a, b) {
  ({
    typeof(a) $a = (a);
    typeof(b) $b = (b);
    $a > $b ? $a : $b;  
  })
}

This is about as far as we should take #define. For more advanced macros we need a new syntax.

Step 4, let’s define non-preprocessor macros using a new macro keyword.

macro foo(&a, &b) {
  int $x = run_foo(a, b);
  if ($x > 0) printf("We got foo!\n")
}

Note the slightly odd “&” prefix. This means we import the entire expression or variable into the scope. Without we simply use the value, so here are the two equivalent versions of max:

macro max(&a, &b) {
  typeof(a) $a = (a); // (1)
  typeof(b) $b = (b); // (2)
  return $a > $b ? $a : $b; // Return automatically makes this an expression statement.
}

// If we do not use &a, &b then we get evaluated values 
// instead, making it look like
// an untyped version of a static inlined function. The macro 
// below is exactly equivalent to the one at the top.
macro max(a, b) {
  return a > b ? a : b;
}

Since macro is largely hygienic, break and return are meaningless in the top scope of the body. For that reason we can reuse return to indicate that the macro returns a value, that is, it should be treated as an expression. This allows us to skip ({ })

Step 5: Wrapping something "inside" of a macro is a pain, so for our final extension, let’s define a trailing body parameter that can be expanded:

macro for_from_to(a, b, macro body) {
  for (typeof(a) $x = a; $x <= b; $x++) {
    body();
  }    
}

for_from_to(1, 100) {
  printf("Again!\n"); 
}

// expands to:

for (int __for_from_to_x_1 = 1; __for_from_to_x_1 <= 100; __for_from_to_x_1++) {
  printf("Again!\n");
}

macro for_from_to(a, b, macro($v) body) {
  for (typeof(a) $x = 0; $x < a; $x++) {
    body($x);
  }    
}

times_do(1, 100) {
  printf("Loop: %d\n", $v);
}

// expands to:

for (int __for_from_to_x_1 = 1; __for_from_to_x_1 <= 100; __for_from_to_x_1++) {
  printf("Loop: %d\n", __for_from_to_x_1);
}

The trailing body is expanded inside of the macro as if it was a macro itself.


In the examples above I’ve tried to extend and expand on the C macros rather than replacing it. The new macro function is simply an evolved subset of #define that can be parsed as normal C except for the lack of types (giving a compiler the ability to issue a lot more errors directly at the macro definition).

This is not the only direction we could have taken the language. Another approach could have been to make it possible to parameterize static inlined functions instead. Those are steps instead of solving a part of the macro problem domain using generic function. In this direction we also have parameterized (generic) structs.

That, however, would bring a significant change to the language. Similarly, a “Rust-like” macro system could offer both expressiveness and safety, but it would be more of a revolution than an evolution.

Sometimes the latter is what you want.

How to procrastinate while working hard

Originally from: https://dev.to/lerno/how-to-procrastinate-while-working-hard-4l5f

Refactoring is an important part of programming. If you are maintaining a non-trivial code base you need to constantly remove cruft and improve on solutions otherwise the code will slowly rot.

Working with improving abstractions and code quality there is also a lure which is mostly ignored, which is over-engineering. The urge to add code that feels “magical” and just does things in an extremely elegant way. You can find examples in amazing C++ templates, or some awesomely elegant Swift code that might use some combination of operator overloading, generics and pattern matching. It might look cool, but over-engineering is dangerous.

It’s dangerous because you can spend days on that “perfect abstraction” which might be elegant on the surface — but your teammates will have a less pleasant time trying to figure out how to debug or extend it later on.

It’s dangerous because all that time you spent might make you reluctant to find easier solutions, or throw it away when it’s no longer needed.

It’s dangerous because that complexity disguised as abstraction is making your code less maintainable and also less easy to understand.

It’s dangerous because you might have thrown away bug free code and replaced it with something new and untested because you thought it might look more elegant.

But most of all it’s dangerous because it’s so damned satisfying to just find those beautiful abstractions. It’s so much fun that we forget how dangerous it is.

So when you feel the urge — remember restraint. The “magically cool things” your language can do are usually exactly those parts that you should stay clear of.

To OO or not to OO

Originally from: https://dev.to/lerno/to-oo-or-not-to-oo-5dm5


title: To OO or not to OO published: true description: A discussion of Object Oriented Design tags: object oriented, procedural, programming languages, java


(a previous version of this text was posted on Medium)

Around 2018, partly due to following Jonathan Blow’s work on Jai and the obvious lack of OO in the language, I started to re-think about whether OO is a good thing or not. I ended up listening to — and reading — various criticisms leveled towards OO and pondered the problem quite a bit. I had also started following Bob Nystrom’s “Crafting Interpreters” series that had the second part written in a very clear and easy to understand procedural C. It reminded me how directly aimed at the problem code used to be for me.

Before OO there was just writing code

I started programming in 1982. I was 9 years old and programming on home computers was almost always BASIC (or assembler, when you wanted to get some speed). It was the era of the first “home computers”. Computer magazines would print listings of games and applications. This was part “getting programs on the cheap” and part “learning to program”.

BASIC isn’t exactly structured programming, at least not those early versions. “Structured programming” was limited to statements like GOSUB 1340. Still, you could definitely build things with it. Games and applications, all were written in BASIC. The limitation was usually the memory of the machine (typically 16 kb) rather than the structure. It might not have been elegant code, but it got things done.

Eventually I would pick up Pascal, and even though later iterations of BASIC would improve much of the 8-bit implementations, Pascal was just so much better. It was more powerful, a more importantly it was really easy to write clear and structured code. But even so, program design just wasn’t much different from writing assembler or BASIC. You started in one end and built things until they were done. It was really easy to just "get things done".

I eventually learned a bit C++, but the OO part of the language mostly escaped me. It wasn’t until I got to Java that things changed — and at the time I believed it to be for the better...

Java and “real” OO

I used to tell people that I didn’t understand object oriented programming until I learned Java. That might not be quite accurate, but it’s true that I didn’t attempt any “Object Oriented Design” until I learned Java.

Java really forced you to do objects. I myself started out when applets was the new hot thing, and the language was still in its 1.0.2 version. It was cool and magical. Objects where these small units you could craft to do things, almost like programming small robots. Instead of just plain programming you did this design where independent objects were talking and producing a result. It was awesome. And of course, it was also a lie.

With Java going mainstream, the articles and books on how to do real OO flourished. The point seemed to be that one should throw away most things about procedural programming. It was supposedly bad in the same way unstructured programming was to the procedural programming. The more we thought in terms of objects, the better things were. It was clear that nirvana was near — accessible to those lucky people who could get program Java for a living.

Object Oriented modelling nightmares

In my free time I was working on the next iteration of a complex online game that was initially written as BBS door (BBS online games back when we used dial-up modems). The original version had been written in QBasic, and I also had done a rewrite in Turbo Pascal that covered about 80% of the game. Writing the game had taken evenings and weekends spread over about half a year.

The QBasic version was tricky as you had to pass all global data explicitly between implementation files, and each file had a size limit — so it had to be split with this huge declaration of globals on top of each file. The Pascal version was so much easier to write. You didn’t need to explicitly pass globals and it was straightforward to pass things to procedures — whereas for QBasic you had to pass parameters and results in global variables you especially set aside for the purpose. Obviously the Java version — with OO goodness — must be even easier than Pascal to implement!

That turned out to be “not quite true”. I wrote page after page on the design of the classes, how each entity would know what, and what state each would contain and how to act on other entities. It felt really cool, but it was also complex and every version of the object model seemed to have some problem where eventually everything needed to know everything and every single class would be hideously complex with little way to ensure consistency. It felt like an impossible task.

Doing real things

Meanwhile I started working professionally as a programmer. I wrote in Perl, Java, learned Objective-C and Ruby. With Objective-C I discovered that the “OO” of Java/C++ was just one brand of OO. In Java the idea was to create tiny classes that were assembled to a whole, but in ObjC objects were instead used as high level “glue” between larger components written internally in straight procedural C code. The fine grained classes of Java would be considered the very opposite of good design for Objective-C. So, if OO was to be to procedural programming as procedural was to unstructured — how come there wasn’t even a consensus on how to do “proper OO”?

My pet project was still a failure. I still tried to model things the Java way and I kept failing. But then I tried writing it in Ruby and something unexpected happened.

Getting things done

In Java it’s easy to just get caught in the work of writing your classes. Java is comparatively verbose, so just writing a bunch of classes, writing getters and setters seems like you got quite a bit of work done. That’s why it was easy just to waste time testing out models and still feel like you’re getting somewhere.

In Ruby, on the other hand, writing a class is trivial. Even modelling a lot of them isn’t much work. If you’re lazy you can even generate a bunch of them using metaprogramming. Ruby is very, very quick to do prototyping in.

Suddenly I couldn’t fool myself anymore. After implementing parts of the model in Ruby it was suddenly clear that I wasn’t adding anything of value by creating those game model classes. It was work, but it wasn’t real work. I needed a new idea.

At the time I was working professionally on poker servers, and it was clear a poker game instance was simply a data structure with the deck, the current bets and players. Player actions were simply commands acting on this data according to certain rules. Maybe this idea could work…? As a prototype in my Ruby code I simply used a nested hash map — no model objects at all. Each action the player would simply invoke the corresponding method which would directly edit values of the branches of the map. A very procedural approach — even though I didn’t think of it like that at the time.

Some good things immediately resulted from this design: it was dead simple to add an “undo”-functionality, where the changes to the data could be rolled back. The data tree was naturally dead simple to serialize and deserialize (compare the headache of my old designs where each single class needed to implement serialization/deserialization on its own), and I could also track exactly what changes were made in order to assemble updates to logged in players.

I had solved my big OO problem… by adopting a more procedural mindset.

Not quite there yet

Despite solving the problem, I didn’t actually realize that the problem was OO. I just thought I had found a very clever solution.

Professionally, even though I could do all the “fancy” OO solutions with reflection, polymorphism etc, my own OO style tended to favour simple, explicit and obvious solutions. But I would pick the solutions because my experience showed me they were the best, not because I realized there was any problems with OO.

A new problem

I had built my game server and it was fine. It was trivial to extend and dead simple to add more features. There was just one problem: the client.

I had written clients before, but on a smaller. As long as you have no more than say 10–15 screens you can get away with most designs. My game client had over 50 distinct dialog screens and many different states. Things were getting messy.

I had my model and my views and my controllers and still I felt I had no control there was just so much state everywhere. Because this is the essence of Java/C++ style OO: split state into small pieces and let each object manage their specific part of the application state. It’s really a spectacularly bad idea as complexity roughly correspond to the square of different interacting states. In addition, it’s very tempting to simply let the state be implicit in some (combination of) member variable values. “Why have an explicit state when you can check the value of a variable to figure it out?”

A conclusion

In procedural programming you tend to keep your state where you can see it. Unlike OO, where you’re encouraged to split the state and hide it, you pretty much have to keep it explicit and that really is a good thing. That’s not to say that using objects is necessarily a bad thing. It’s a very powerful tool for building UI rendering hierarchies for one thing, and the namespacing together with chaining can create very smooth and readable code, compare: urlescape(substr(string, 0, strlen(string) - 2) to string.substr(0, -2).urlescape() (there can still be an argument that the former is clearer though!). However, the object oriented design with objects that keep state or act on other objects — here is where OO goes all wrong.

There is also the (mostly forgotten) Objective-C style of OO which happens to be even better for building GUIs than Java/C++, as the late binding of the dispatch and the runtime pushes it significantly closer to being a scripting language. Sadly Apple, the former champions of Objective-C, have largely forgotten what the idea behind ObjC really was, and are now replacing it with Swift which adopts the OO style of Java/C++.

Still, there are languages trying to get back to the basics. Golang is one, and many of the other new “system programming languages” also qualifies. Go in particular (despite my reservations regarding the language) disproves the myth that “it’s not possible to build large scale products with procedural programming”. The increasing popularity of the language might create a crack in the idea that OO is “inevitable”

However, Java-style OO is deeply entrenched and shows little inclination of disappearing anytime soon. It will be interesting to see what the future brings.