Overall though this isn't something I've generally run into too much with Elixir.
Overall though this isn't something I've generally run into too much with Elixir.
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
Clojure and `reitit` (https://github.com/metosin/reitit): (def router
(r/routes
[["/hello" {:get (fn [r] {:status 200 :body "world"})}]
["/users" {:name :users
:router users-router}]
["*" {:get (fn [r] {:status 404 :body "oops"})}]]))
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.
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.
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?
1: https://github.com/erlang/otp/pulls?q=is%3Apr+author%3Ajosev... 2: https://hex.pm/ 3: https://hexdocs.pm/recon
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.
I could probably keep going.
There are lots of silent fans of your teams' work. Please keep it up, and please feel proud of your collective outputs!
It is. That they have not replied to any of your responses shows they don't have much argument.
I mean using "factually" in this subjective opinion is just ridiculous.
> I just think it's factually much worse than the Clojure example
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.
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.
https://github.com/elixir-plug/plug/blob/main/lib/plug/route...
Everything else is explicitly passed, AFAICT.
My huge beef with plug is that it creates an implicit conn variable in all of the plug bodies.
But yeah, phoenix is a combination of good choices and some real stinkers, and I did mention it.
Edit: I forgot about match. match "isn't great".
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.
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.
- 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.
Anyways my prediction is about Non-core++ libs, which I listed in gp.