5 ms·
> 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
by 59nadir 3y ago
> 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
Clojure and `reitit` (https://github.com/metosin/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.
- dragonwriter 3y ago> 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.
- 59nadir 3y ago> “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.
- hosh 3y agoSure. 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.
- josevalim 3y ago> 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? 1: https://github.com/erlang/otp/pulls?q=is%3Apr+author%3Ajosevalim+is%3Aclosed https://github.com/erlang/otp/pulls?q=is%3Apr+author%3Ajosev... 2: https://hex.pm/ https://hex.pm/ 3: https://hexdocs.pm/recon https://hexdocs.pm/recon
- troupo 3y agoElixir was the kick that Erlang needed for a long time.
- throwawaymaths 3y agoActually I think plug is mostly fine since it only brings in one macro (plug) that isn't an http verb, or http common concept (forward e.g.). 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".
- dqv 3y ago>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.
- throwawaymaths 3y agoyes sorry, I meant Plug.Router macro bodies. The rest of plug is fine.
- arrowsmith 3y agoMaybe it's just down to familiarity, but I find the Phoenix example more readable than the Clojure one. To each their own I guess.
- deleted 3y ago[deleted]
- no_wizard 3y ago`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.
- halostatue 3y agoYes, it’s a bit of a magic argument, but it’s the only unhygienic variable introduced with a Plug dispatch: https://github.com/elixir-plug/plug/blob/main/lib/plug/router.ex#L582-L589 https://github.com/elixir-plug/plug/blob/main/lib/plug/route... Everything else is explicitly passed, AFAICT.