3 ms·
Macros are nice when you are designing a DSL for a framework or a library. otherwise, they should probably be avoided.
by weatherlight 5y ago
Macros are nice when you are designing a DSL for a framework or a library. otherwise, they should probably be avoided.
- dnautics 5y agoYeah. It's super tempting to use macros for inheritance (and there are some anointed frameworks that even encourage that -- I'm looking at you ExUnit), but I think that is the wrong way to think of macros. The codebase I work on at work is littered with this antipattern, but I grin and bear it because elixir is such a joy to work with otherwise.
- waynesonfire 5y agoeverything is a DSL / framework / library. I'm starting to dislike this idea. The claim is a general programmer should avoid macros, but, some blessed developers are granted this privilege and thus have the tools to create useful -DSL / framework / library- code because they used this advanced capability. And useful code tends to become popular. It's a difficult pill to swallow when a an engineer comes in guns blazing trying to enhance your baby project with macros and thereby introducing a ton of new complexity. This is the issue. Macros are like a developer-ux enhancement. You better have your core library figured out pretty well before investing in this enhancement and most projects never reach that level of maturity. Especially, internal ones. It's not that macros should be avoided, they just need to be introduced at the right time.
- klibertp 5y agoMacros in Elixir are a code-generation facility that happens to be Turing-complete, nothing more. You should use it whenever code generation makes sense. Code generation comes with a set of well-known problems, for example the error messages and stacktraces need special attention. If you feel that the advantages of code generation outweight the disadvantages in your specific situation, you should use it, no matter if you're designing a framework or writing a one-off script. One of the advantages of code generation is hiding the details of implementation, so that they can be changed more easily (because the description of the whole is shorter, and kept in one place). While you get the same benefits in Object Oriented languages via other means (interfaces and abstract classes, Template Class pattern), in Elixir you don't have much choice: you either implement a callback module (ie. behaviour; but then there are cross-module calls at runtime) or generate the code via macros (but then you lose the 1-to-1 correspondence between what's in the file and what's actually defined in the module). Of course, both can be (and frequently are, like in GenServer, Application, Supervisor, etc.) used at the same time. Not using macros until some point in time in the development (after it "reaches that level of maturity") risks getting that point in time wrong. It almost always will be wrong, actually, and more often than not you'll find that it's too late: introducing macros after first spending time on optimizing the functional interface makes you just do the same work for the second time, at which point the improvement in "developer ux" may not be worth it or even noticeable. Erlang doesn't have (well, it does, but not as easily accessed) macros, and the libraries still get written in it - frameworks too - and there's little point in adding macro-based wrappers (beyond some simple default implementation injections) to them, because their functional interfaces are already optimized from the dev UX angle. So, to make the most out of macros, you probably shouldn't wait until "after you're done" with something, but you should introduce them as soon as you see a good use for them, while considering all the potential downsides and pitfals that come with their use. Also of note, while you can compare macros to eg. Python (or Ruby, or Smalltalk) Metaclasses, there is a difference: Python offers a lot of mechanisms that you should prefer using over metaclasses if they would suffice. On the other hand, there's not really much you can use in Elixir, as it's a lot smaller and simpler language. So, while I don't think many Python programmers should be writing metaclasses (though all should know what they are!), I think almost all Elixir programmers should be able to use macros. The macro system in Elixir, BTW, is actually on a simpler side of Turing-complete macro systems, both defmacro from Lisp, syntax-case from Scheme, and imperative macros in Nim (and many others) are all much more complex and harder to use. So I think the Elixir designers wanted people to use macros and made an effort to make it safer and simpler. That's just my impression though, don't quote me on this :)
- dqv 5y agoI don't really like the dogma of "don't use x". It's much better to just show examples of when macros are useful and when they're not. I got burnt trying to do tenancy. At one point I was like "I'll use the process dictionary", but had it hammered into my brain from everywhere "don't use the process dictionary!" It turns out the process dictionary was exactly correct for my use case. This is just an example of one of those dogmas that needs to be more like "x is a bad use case if you're trying to do y, but not if you're trying to do z". Here's an example of when macros are very useful: months = Enum.map(~w(january february march april may june july august september october november december)a, fn month -> {month, to_string(month)} end) def to_month(arg) for {atom, _string} <- months do def to_month(unquote(atom)) do {:ok, unquote(atom)} end end for {atom, string} <- months do def to_month(unquote(string)) do {:ok, unquote(atom)} end end def to_month(_), do: :error I can already hear someone saying "well you can do the same with functions" but it starts to get ugly and you're now doing these conversions at run time. A function-oriented approach would need guard clauses and run-time checks of an element being in a list of atoms OR strings then possibly run-time conversion of the strings to atoms. Or you can just use macros.