8 ms·
Yep. The only problem with elixir macros is that people look at the macros that are in core++ (exunit, plug, phoenix, ecto) and decide it's a good idea to dsl-
by throwawaymaths 3y ago
Yep. The only problem with elixir macros is that people look at the macros that are in core++ (exunit, plug, phoenix, ecto) and decide it's a good idea to dsl-up everything.
The biggest offenders IMO are commanded, Tesla, absinthe, exmachina and now most recently ash. Then there's tons of tiny libraries that litter hex.pm with macro-heavy nonsense.
At least the core++ libraries make macros whose syntax/keywords matches something outside elixirland that you'd have to know anyways (e.g. 'get' from plug, all of SQL from ecto), but the big offenders list are making up clever names for things, which adds mental overhead to literally everything you do, building crazy complicated compilation pathways (especially for overeagerly lazy stuff) or writing function names from whole cloth that make refactoring or searching your codebase a nightmare (or more than one). E.g. if you're working in a domain where non-proud camel case is the convention, don't automatically snake-case it.
- freedomben 3y agoYep 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.
- throwawaymaths 3y agoMark 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".
- cschmatzler 3y agoI’m pretty sure that the libraries you consider core++ such as ExUnit and Ecto are considered feature complete and won’t get any bigger.
- throwawaymaths 3y agoCorrect, but remember core++ seeded atrocities like ExUnit.CaseTemplate. Anyways my prediction is about Non-core++ libs, which I listed in gp.
- josevalim 3y agoI was never fully pleased with ExUnit.CaseTemplate. If you have suggestions I am all ears. :)
- throwawaymaths 3y agojust yeet it and have people do it manually. To be honest, it's not fatal for the ecosystem. It will just be slightly less enjoyable over time.
- johnisgood 3y agoI'm sleep deprived. Would not it rather be "start" saying "don't use macros", given that macros are "bad"?
- josevalim 3y agoEh, 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.
- throwawaymaths 3y ago> Decimal, Jason, Telemetry, Mint, Finch, Req 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.
- 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 agoNot 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.
- sodapopcan 3y agoI personally don't have any problem with heavy macro-use in libraries, I feel like that is where they shine. So long as the documentation is good, I don't really care how the library itself is implemented. I would much rather have the cleaner API in many cases. I know it can be taken too far, though. Some funky stuff can happen in Phoenix's router, though a lot of it is actually to reduce cyclic compile-time dependencies as the router is a place where these necessarily happen. Macros in business code, however, are pure evil. I feel like anyone who spent any time in Rails over the past decade has learned that lesson.
- throwawaymaths 3y agoYep. I wish `pipeline`, `scope` and `resources` in Phoenix router and also all the `use MyApp.Web, :x`, would just die in a fire. Thank goodness they got rid of views though I still am annoyed that the default path resolution on templated content isn't explicit. The crazy thing is that the most amazing part of phoenix, liveview, is almost no macros.
- sodapopcan 3y agoI never use `resources` though have no beef with `pipeline` or `scope`, though I haven't thought too much about it. It is a little weird getting used to which modules are getting auto-qualified and which aren't, but I'm long passed that. And that is actually what is "fixing" the cyclical dependency issue. What's wrong with the `use MyApp.Web, :x`? You can easily get rid of it as it's just generated boilerplate. It's a nice little pattern to allow all the different things to share the view helpers. If you'd like to be extra explicit, though, just delete the file and include everything manually. I'm indifferent on views because I never really used them, haha.
- halostatue 3y agoI personally would prefer something like: defmodule MyApp.Web.Router do use MyApp.Router end over defmodule MyApp.Web.Router do use MyApp, :router end but that’s a preference.
- bmitc 3y agoAs someone who loves Elixir and knows it, this is one thing that really makes it hard to learn Phoenix for me personally. I have struggled learning Phoenix because it is so heavy with macro upon macro that it feels like learning another language.
- throwawaymaths 3y agoI have some bad news for you: elixir ecosystem is consultancy-driven ecosystem. While at least better than bigtech-driven ecosystem (cough cough), the changes you seek are less likely to happen because 1. They make it easier for consultancies to templatize what they do, which: - keeps their employees tied to the hard earned knowledge - keeps their clients tied to their services At least though you know that those patterns are (usually) common and battle tested (or, at least, will be battle tested - cough cough ash), and unlike a bigtech "their problems are likely to be your problems".
- nkjnlknlk 3y agois the claim Ash is not battle tested?
- josevalim 3y ago> - keeps their clients tied to their services I have heard this claim so many times but it is easy to prove it wrong given how much we focus on documentation and understandability around Elixir and main libs. I have had clients asking me questions and I would answer it by improving the docs or writing a guide, committing it to the repo, and then asking the client for feedback. Half of the Ecto guides were written like this. It is not about keeping clients at all. It is about having a great on-boarding (and beyond) experience.
- bmitc 3y agoI second the notion that the Elixir documentation is generally great, especially the libraries and ecosystem bits. It is clear it is taken seriously and cared about. The level of pro-activeness of the Elixir core team is definitely something that's noticed and personally draws me to the language.
- troupo 3y ago> now most recently ash I've been using Ash in a side project and it's slowly growing on me. You have to twist your brain a bit to grok it (and I haven't, not fully). Pros: - a lot of things happen where you want them to happen. I found that writing stuff with Ecto involves more boilerplate than with Ash because you have to write your queries, and your changelogs, and your API accesses, and wire them together, but what if you want a separate API.... With Ash it's a single resource with a few macros. - quite a few escape hatches into regular Elixir You don't want magic behind actions? There are several ways to hook into what's happening, or even write everything manually. And to the calling code... it will still look the same - Despite all the macros the errors are usually quite good and pinpoint the problems Cons: - too few people who truly understand Ash - documentation is often too terse and concise. But you can't really blame the author who is doing a lot - errors are not always good :) I do agree with you on overreliance on macros in parts of the ecosystem
- throwawaymaths 3y agoI am working at a company that uses ash and have a data science side project that doesn't. The data science project did a sql query that I couldn't figure how to optimize in SQL and I turned it into simple elixir code. Queries that took minutes now took seconds. Now, I know that ash can do both but I don't know how I would diagnose that problem or test alternatives in ash, because it hides way too fucking much of the underlying code.
- troupo 3y agoI guess you'd do it the same way: look at logs, write an optimized query manually :) But I do get what you mean.
- halostatue 3y agoI haven’t used commanded, exmachina, or ash: - Tesla has a mode which can be used completely without macros, and I am increasingly encouraging that it be the only way that it is used. So does the author (as of 2020): https://github.com/elixir-tesla/tesla/issues/367#issuecomment-610342739 https://github.com/elixir-tesla/tesla/issues/367#issuecommen... There is also `req` mentioned in a recent post as an alternative (it looks good, but I am still playing with it to see if it is a suitable replacement for Tesla in all cases). - Absinthe is something of a compiler itself, because it has to strictly define things the way that is specified in the GraphQL spec. You can now import an SDL file, but you still need to hook resolvers and middleware into it. Honestly, I don’t think that the schema definitions in JS/TS are much better for GraphQL in terms of readability. Being heavily macro-based means that there are sharp edges that are harder to work around when you want to add your own macros for code reuse purposes. That said, aside from the schema definition, Absinthe is entirely usable without macros. Within the schema definition, Absinthe isn’t making anything up, it’s using the same basic definitions that the GraphQL spec do, adapted for Elixir syntax. Exmachina didn’t interest me because I don’t think much of factory_bot (which used to be called factory_girl), as I saw it abused far more than used well (IMO, it’s impossible to use correctly). Ash…looks like an interesting experiment, but I don’t know that there’s a lot of pick-up with it compared to Phoenix. And I have yet to find a use for CQRS/ES, so there’s no reason for me to play with commanded. I certainly wouldn’t consider any of these three to be "major" players in Elixir. Tesla and Absinthe? Yes.
- throwawaymaths 3y agoYes, sorry didn't mean to claim all of the libs do all of the sins: absinthe definitely didn't make up words. The circular compiler dependencies of absinthe really ticked me off. Was on a project where one small change elsewhere would mysteriously trigger absinthe recompilation which would trigger even more recompilation. Might or might not been fixed in one of the newer compile-slim elixir releases