Yep this is definitely a problem, but fortunately the culture/community around Elixir generally tends to frown on unnecessary macros. I heard the saying, "if it can be a normal function, it should be" many times, which pleases me.
Overall though this isn't something I've generally run into too much with Elixir.
> Yep this is definitely a problem, but fortunately the culture/community around Elixir generally tends to frown on unnecessary macros. I heard the saying, "if it can be a normal function, it should be" many times, which pleases me.
While Elixir people usually say this, they don't at all follow it in practice because of the terrible example set by all the libraries. Phoenix is especially egregious and I can't believe the grandparent didn't bring it up.
Looking at Clojure's solution that is (as far as I can see) very popular it makes me annoyed at the relaxed attitude towards macros in Elixir and how bad it actually is:
Elixir and `Plug.Router`:
defmodule MyRouter do
use Plug.Router
plug :match
plug :dispatch
get "/hello" do
send_resp(conn, 200, "world")
end
forward "/users", to: UsersRouter
match _ do
send_resp(conn, 404, "oops")
end
end
basically everything in the Elixir case is a macro and non-standard syntax whereas literally of the components of the Clojure case are just standard data types, and yet it manages to be more readable.
> basically everything in the Elixir case is a macro and non-standard syntax whereas literally of the components of the Clojure case are just standard data types, and yet it manages to be more readable.
“Readable” is mostly a matter of “fits the grooves already worn in my brain”; to me, the Elixir there nonstandard-macro-based-syntax-and-all, is vastly more readable than the Clojure.
> “Readable” is mostly a matter of “fits the grooves already worn in my brain”; to me, the Elixir there nonstandard-macro-based-syntax-and-all, is vastly more readable than the Clojure.
I'll admit to being able to read Lisp syntax without choking on the parens, but I don't even use Clojure (or any other dialect that uses special syntax for the different collection types) and I think it's way more readable and malleable (you can literally do whatever you want that makes sense to this data and it'll work, meaning you're free to write whatever middleware you want that takes and produces the expected data).
I've used Elixir since 2015 so I have no problem actually reading the syntax at all; I just think it's factually much worse than the Clojure example because it's basically just a bunch of macros you can't do much with and lots of implicit behaviors.
This also applies to Elixir's greater position in the BEAM ecosystem. I think the idea was that Elixir was going to be a boon to Erlang and other languages in there over time but I think it's demonstrably pretty useless as a BEAM citizen except for some of the patches they've submitted; most big Elixir libraries can't even be used outside of Elixir because they're full of macros and don't even provide interfaces with just functions.
> I think the idea was that Elixir was going to be a boon to Erlang and other languages in there over time but I think it's demonstrably pretty useless as a BEAM citizen except for some of the patches they've submitted
Not sure whose idea it was but I would say this is a very inaccurate take. Of course, we have taken much more from Erlang than given (that's expected from hosted languages), but I personally sit on more than 100 PRs to Erlang/OTP. I made binary matching several times faster by using SIMD. I improved the compiler performance (including Erlang programs) by (conservatively) 10-20%. I contributed compiler optimizations to reduce memory allocation. There is a now faster sets implementation which I contributed. Erlang/OTP 26 ships with faster map lookups for atom keys based on a pull request I submitted. The new logger system in Erlang takes ideas from Elixir logger. The utf-8 string handling in Erlang is based wholesale on Elixir’s (and extended for lists). And this is in no way a comprehensive listing [1].
And this is not counting everyone else in the Elixir community who contributed and those who are now actively working on Erlang tooling and were on-boarded to the ecosystem via Elixir. The Erlang ecosystem now has a package manager [2], used extensively by both languages, rebar3 and Mix, all implemented and powered by Elixir.
And then there are efforts like the Erlang Ecosystem Foundation, which is made of developers and sponsored by companies working with both languages, and it delivers important projects around observability, documentation, and more. For example, you can find several Erlang projects now using ExDoc for documentation, such as Recon [3].
That’s my take. What are your demonstrations that we are pretty useless for the BEAM ecosystem?
Hell i could add more. The current best json lib in erlang is a literal (called out for it in its readme) copy of the Jason elixir one in erlang.
My hyperloglog erlang lib is one of the most advanced in any language (behind what Omar Ertl does for Java) and i come wholesale from elixir.
I have my own code, as an elixir person, in OTP, for the formatting of float to string. The maybe construct was only possible because elixir with showed the way.
Elixir is a fantastic effort, and the criticism the GP comment made is in no way justified. I'm sorry you are taking flak as an open source programmer for something so far from the truth.
There are lots of silent fans of your teams' work. Please keep it up, and please feel proud of your collective outputs!
Honestly, I don't care if we were a kick or not :) but saying we are "demonstrably pretty useless" is completely unfair and bordering on being a bad faith argument.
> most big Elixir libraries can't even be used outside of Elixir because they're full of macros and don't even provide interfaces with just functions.
I was just reading the documentation, where it says "remember that macros are not your API", and smirking. If you write macros and things depend on them, such that if you change the macros, things will break, macros are your API. Syntax is API.
Sure. But there were improvements to the ecosystem. Telemetry is a great example, though they ended up porting it to Erlang so that both Erlang and Elixir projects can use it.
`conn` is a magic argument isn't it? Like an implicit argument to this block of code?
That is one thing that really bugs me, is I can't automatically see where the arguments are coming from. I realize it makes code a little more verbose, but a requirement around explicitly showing the dependencies used is much more useful when you're refactoring, for instance, or scaling code.
not a hard requirement by any means, sure, and it definitely doesn't mean something won't scale as it were, however I am constantly frustrated by languages that allow for implicit scoped dependencies / arguments.
>My huge beef with plug is that it creates an implicit conn variable in all of the plug bodies.
What do you mean? That only happens if you use Plug.Router; otherwise, you have to define a call function that takes the conn as the first argument. You can even go as far as explicitly calling the init functions for each Plug manually. It's init(opts), call(conn, opts) all the way down.
Not to mention that the macros should be invoking regular functions, making it easy to skip macros if you so choose.
When I pivoted from Ruby to Elixir, I was expecting to do a lot more metaprogramming, but I found that regular functions with pattern matching already does a lot that I want for many cases.
Mark my prediction: as the big "non-core++" libraries get bigger and trashier with their macros, and more used by the community, hex.pm will become littered with even more trashy macro libraries, and people will stop saying "don't use macros".
Eh, there is also plenty of indication this may not happen. :)
- Many of the non-core++ libraries are considered done
- Many popular and upcoming projects have zero macros: Decimal, Jason, Telemetry, Mint, Finch, Req, etc
- All machine learning projects have either zero macros (Axon, EXLA, Bumblebee) or the macro layer is built on top of the functional API (Nx and Explorer)
- Phoenix LiveView, which is the most recent project from the Phoenix team, is very minimal on the use of macros
- Most of the Phoenix sub-projects have zero-macros: phoenix_pubsub, phoenix_html, phoenix_ecto, esbuild/tailwind, etc
I mentioned this in another comment but in my opinion Phoenix is low on the use macros. They are present in the endpoint+router but then you are working with functions. I speculate the reason why they stand out is because they are the entry-point of your code. But even then the socket API in the endpoint is being replaced by a functional API thanks to contributions from Mat Trudel and the new verified routes are way less magic than the previous route helpers.
These are all truly fantastic Non-core++ libraries. Also some really good ones on the horizon like Bandit.
But I'm love elixir and I'm not calling out the good ones. I'm calling out the less good ones. Because I see a lot of young libraries that pick up on those patterns, and so I can only believe it's spreading (can't be bothered to actually quantitfy, sorry). I don't think I'm bringing a curmudgeon. Legibility and debuggability are real concerns.
Overall though this isn't something I've generally run into too much with Elixir.