Not sure I understand what you mean and it may just be my personal preference but I like to have a very clear understanding/separation between generative, compile time and runtime effects. Preprocessors often aren’t considered “part of the language” and their syntax is typically so different that it’s very clear what has an effect before the code hits the compiler.
In Elixir’s case macros are such an integral part of “the language as it’s employed” that it takes a lot more conscious effort to differentiate “source code” with compile time effects versus runtime effects.
The confusion experienced by newcomers is simply a warning sign that they are approaching Elixir with an ill-fitting mental model. As such that confusion should be exploited as an opportunity to explain in what ways Elixir is different from other languages.
So while superficially def looks like a keyword from Ruby or Python the moment they look at the documentation (which I think isn’t a completely unreasonable expectation) alarm bells should go off.
def(call, expr // nil) doesn’t look anything like “function definition syntax” - I’d say it looks more like a code generation directive. This is also the perfect opportunity to drive home the fact that imperative languages make heavy use of statements while functional languages tend much more towards expressions.***
Once newcomer’s have sorted out that difference, they’ll comfortably fall back to treating def ... do ... end just like a “definition” while not being confused by def ... , do: ... and they’ll also be better prepared to deal with any other misunderstandings that they are bound to discover in their journey ahead.
*** I find it discouraging how often Elixir/Erlang educational material uses the term “statement” when “expression” is correct. For example, there is no case statement, only a case expression - if you choose to ignore the returned value then that is your own business.






















