Reflection — An Honest Take 8 min

Honest Take — Before You Begin

Honest reflections on metaprogramming done right and architecture that scales


Elixir's metaprogramming is more principled than Ruby's. This matters more than it sounds. Ruby gives you method_missing, define_method, class_eval, instance_eval, send, open classes, and refinements. It is powerful and it is dangerous. You can redefine anything at any time. You can make code do things the original author never intended. Rails uses this power extensively, and the result is a framework that feels magical until something breaks, at which point the magic becomes a debugging nightmare. You have lived this. Years of it.

Elixir has macros. Macros operate at compile time, not runtime. They transform the AST (abstract syntax tree) before the code ever executes. This means metaprogramming in Elixir is explicit, traceable, and visible in the compiled output. There is no runtime surprise. No method that exists only because something somewhere called define_method in a callback. No class that behaves differently because a gem monkey-patched it. When Elixir code does something unexpected, you can trace it to a macro expansion. When Ruby code does something unexpected, you might need to search the entire dependency tree.

The philosophical difference: Ruby metaprogramming says "the programmer is smart enough to handle this power." Elixir metaprogramming says "the programmer is smart enough to not need unconstrained power." Less magic, more power. The Elixir community even has a guideline: do not write a macro when a function will do. Macros are a last resort, not a first tool. Compare that to Rails, where metaprogramming is the first tool for everything.

Umbrella projects are the other revelation in this module. The "modular monolith" is a pattern the Rails community has been chasing for years -- Shopify's component-based architecture, Basecamp's use of engines, various gems and conventions for drawing boundaries within a single application. In Elixir, umbrella projects provide this out of the box. Each sub-application has its own supervision tree, its own dependencies, its own test suite. The boundaries are enforced by the build tool, not by convention. You cannot accidentally reach across boundaries because the compiler will stop you.

For someone who has watched the Rails community struggle with "how do we organize a large application" for the better part of a decade, umbrella projects feel like an obvious solution that was always available -- just in a different ecosystem.

Conclusion #

Advanced Elixir is where you stop being a beginner who writes working code and start being a practitioner who writes idiomatic code. The macro system teaches you that metaprogramming does not have to be dangerous. Umbrella projects teach you that modular architecture does not have to be a convention you hope people follow. Both lessons are directly applicable to how you think about software design, even when you go back to Ruby.

Predictions #

  • You will write your first macro, feel powerful, then immediately understand why the community says "do not write macros unless you have to"
  • Umbrella projects will make you retroactively frustrated with every Rails monolith you have worked on
  • Understanding the AST will give you a deeper appreciation for how Ecto queries and Phoenix routes work under the hood
  • You will start evaluating Ruby gems differently -- asking "how much runtime metaprogramming is in here?" as a quality signal
  • Protocols and behaviours will become your preferred abstraction mechanism, replacing the inheritance hierarchies you are used to in Ruby
Learning resources 7

That's the free preview. Sign in to continue this course.

Sign in to continue

New here? Make a desk →