38 comments

[ 3.3 ms ] story [ 17.2 ms ] thread
I really love Elixir but our company had to drop it and rewrite a few projects in Go and Ruby, was too hard hiring and the existing team preferred not to continue with it. I think the learning curve is also quite harder than it initially seems, might be why some people give up on Elixir. But nothing but respect for Elixir.
I don’t know why you are getting downvoted. I think this is a valid criticism that should be discussed and not ignored. I find elixir to be quite elegant to work with but that’s just like my opinion. I guess my question is what about it gives it the steep learning curve?
My team has had success training experienced devs in Elixir that were coming from other backgrounds. I would caveat this with the fact that we just have a Phoenix app with hardly any code that leverages OTP.
Which concepts did you find that the team struggled with?
(comment deleted)
Having trained a couple of people on Elixir, I have had good success.

I believe there is more a "critical mass" coupled with "matter of taste" issue, which I hope will be solved as more good stuff comes out (e.g. graphing support via Vegalite for Livebook as announced yesterday https://twitter.com/dashbit/status/1395763964215185409).

I believe a number of people would currently like to get paid to work with Elixir, and hope the critical mass will be reached soon.

I’ve seen this a lot. The biggest reason for failure is people assume they already know everything about the VM and language. Erlang + BEAM are the most integrated pair of VM and language I know of. It’s the only VM that will preempt running processes. It’s also truly functional.

A lot of really brilliant folks have written truly shit Elixir code because they underestimated the challenge. Oh you’re passing that blob to four different gen servers? Cool expect you blew out your memory copying terms between them. It’s also operationally complex and doesn’t like the normal way of doing things because quite frankly it’ll do it better.

Take metrics or logging, both will slow down your application in Elixir/Erlang/BEAM by a not insignificant amount but people just shovel as much as they can into their apps assuming truths from other languages still hold. When your request times are measured in microseconds, that instrumentation is gonna slow you down. People literally buried boxes with erlang on them in the ground and they’ve worked for 20 years. Maybe give sampling a shot if it worked for a telecom maybe it’ll work for your blog :p

I’ll stop rambling here. While I truly love elixir it’s a lot more of a rodeo than people expect and it extremely subtle ways. It’s also extremely expensive to hire developers. Because you’re not going to be able to hire juniors. They just don’t exist for a language like Elixir.

Yes. I view Elixir kinda like 2 different languages in 1: the functional language and the process language. The process language is the most unique one though so you will probably spend more time learning that than the functional one.
It's 3 languages. The functional language, the otp process system, and the compile-time scripting language.

I think people who have difficulty with some of the constructs at the just-inside-the-defmodule scope are not understanding concept #3

We train people on Elixir where I work and it seems to go well. I think I was one of the only people with prior Elixir experience they've ever hired, and I am arguably a junior.
Is there a public blog post about this with specific issues? Or is this just trolling?
As a counter, I built an elixir team from 0-15 people and the devs love it.

No particular problems hiring - finding fantastic devs is always hard, but lots of people we hired were attracted by the language.

> was too hard hiring and the existing team preferred not to continue with it.

The second problem is bigger than the first.

For hiring, I worked at a company running Erlang for our main server app; very few people were hired with Erlang knowledge, almost everyone had to learn it on the job. As an earlyish hire, I had the most experience before being hired because I sort of remembered seeing a Slashdot post when Ericsson open sourced it; but I didn't do anything with it until I was hired. The couple of big issues this lead to is we never really did anything useful with the OTP application concept, no releases and what not, just hotloading with l/1 in the shell; and maybe it took us a bit longer to figure out how to scale BEAM because we didn't have anyone with that experience... But BEAM is written so nicely that people with experience optimizing C stuff can dig in and optimize BEAM without a huge learning curve, and most of the bottlenecks fit the same pattern of something simple that works fine at reasonable volume, but blows up consistently and clearly when you run it at large volume, so pretty soon you learn the pattern and know what to look for... And of course 24 has a lot less of these bottlenecks than r14 did.

Having learned Elixir, there definitely were a couple interesting gotchas, what kept me sane was at least already understanding the Actor model and message passing. Having to learn a new language alongside a new paradigm can be very challenging.

My personal favorite Elixir gotchas I've witnessed are, trying to run Mnesia on only 2 nodes, and OOMKiller doing interesting things where somehow parts of your app die.

I don't like the operators' logic in elixir. There should be one for assigning variables, one for pattern matching and there is no rebinding of variables.

Could be like that:

  // assigning

  x = 1

  // pattern matching

  x ^ 1

  // pattern matching and assigning

  x =^ 1
And they should remove the rebinding that is useless. But I find it very difficult for that to happen at that time.

Automatically translated.

(comment deleted)
Metaprogramming is so much easier in an sexp type syntax than in any other language, I really do wish it was more common
I think that the metaprogramming situation is a little problematic in Elixir. Elixir makes heavy use of macros in basic libraries like the Phoenix or Ecto frameworks that are used by more or less everybody.

I understand that macros are useful to build a DSL for a specific project but using macros in basic libraries like this leads to "way too much magic": Trying to understand (or somehow extend) Ecto is not really possible for all practical purposes and following the control flow in Phoenix is like a maze because of all the macro substitutions. This also leads to cryptic errors where you get an error in non existant lines of code.

This heavy of macros is my main complain about Elixir and I think it's one of the main problems that new people face when trying the language resulting in either not using it at all (or if they start it they will abandon it).

Finally, I understand that using macros in these libs allows for much more compact (and maybe beautiful) code, however I will always pick a more verbose (but less magic) version of this if I had the choice. I think this is the case for most programmers that want to use a language as a tool to do their job.

I agree. I was trying to learn Phoenix but had a hard time with the DSL because it was so non-functional in appearance.
Are you talking about routes or Phoenix.HTML? Those are the only two places I can think of having a DSL. Phoenix.HTML isn't strictly required and the routes look like data to me.
Not sure if I would consider Phoenix basic. As far as I know, it's a wrapper around (and configurator for) Cowboy and Plug. I would consider Cowboy basic and Phoenix advanced.
It depends on what you mean by extending Ecto.

I've read bits of the source code to better understand what commands are doing, and you're right you can't use an IDE to jump from the public api to the internal implementation. However, the project has put a lot of work into an extremely clear internal naming convention, so you can just go to the implementation directory and grep for the public api name.

If you mean writing your own command that composes like the builtin ones, that's extremely easy. Often you can do it with just a plain function, and rarely you need a macro. Suppose you have something like this (haven't written elixir in a while, syntax may be slightly wrong)

    def where_admin_over(q, age) do:
        where(q, [u], u.age > ^age and u.is_admin)
    end

    def foo do
        from(User)
        |> where_admin_over(18)
        |> where([u], u.country == "de")
        |> Repo.all()
    end
It's also quite easy to make that custom command run raw sql.
Not sure I agree on Phoenix. There are macros, but only around plugs and routing, not much different to what you’d find in Django and definitely less magic than Rails.

Plugs are straightforward function pipelines and a declarative DSL really helps structure your routes.

The controllers and channels, which are responsible for coordinating with your domain layer, don’t really add anything in the macro department. Plus there is no inheritance, which in my opinion is the biggest source of confusion on most OO frameworks, often requiring you to chase a behavior up and down the class chain.

I see this criticism a lot but I don't think it has anything to do with macros specifically and more so to do with lack of familiarity with Elixir. I've felt the same way about Django being magic because I had trouble following the class hierarchy. It makes a lot more sense now because I'm more familiar with Python and Django. But even today I'll be looking deeper at something and ask WTF it's doing. In that respect, Elixir codebases are easier to me. The module depth seems "shallower" and I don't have to disambiguate between what behavior is caused by class inheritance or an imported function.

When I first tried to use Elixir several years ago Ecto.Schema [0] seemed complex and magical, but then I came to realize it's just converting module attributes to runtime code. There is not really that much complex macro logic going on.

>This also leads to cryptic errors where you get an error in non existant lines of code.

When was the last time you used Elixir? This isn't a problem I can recall having in the last 4 years or so of using Elixir.

>following the control flow in Phoenix is like a maze because of all the macro substitutions.

Can you clarify what you mean by this? A specific case as to where this happened for you would help. Phoenix's use of macros is actually pretty light [1] except for some very low level stuff. You can even see how frequently a developer will use macros in Phoenix by searching `__using__` in the codebase [2]. It's not used as much as people think. The majority is for views and controllers and only to provide a very thin layer of support on top of your regular use of code. As an example, the "macro magic" in Phoenix.Controller is just handling some basics for giving a layout and view to Plug and handling fallback actions for exceptions. You could do the plug calls manually and I think it would be safe to not use any macros in your controller code.

Another familiarity issue with the language (and any language really) is understanding what is meaningful in a stack trace and what isn't. And the likely cause of the error in the first place. Is it syntax? Is it mistyping a variable? Is a function just used improperly? (wtf is init_p_do_apply and why does it show up in every stacktrace?) You're juggling all these different issues - learning a new paradigm, a new syntax, not knowing how to extend things. It's obviously going to be a little overwhelming and, if not strictly required, we might just pick a different language that we're more familiar with.

[0]: https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/sch...

[1]: https://github.com/thechangelog/changelog.com/blob/master/li... (not mine, I just go here to show the most frequent use of macros in Phoenix)

[2]: https://github.com/phoenixframework/phoenix/search?q=__using...

Macros can be de-mystified with proper editor support. In Emacs and Lisp you can (recursively) use "macroexpand" to see what code it produces in-place. I am not an Elixir user (though thinking about hopping on), apparently there is alchemist.el[1] that lets you do that (with caveat, and the project seems dead tbh).

[1] https://github.com/tonini/alchemist.el

Macro expansion has recently been added to elixir-ls[1] and is supported via custom command in vscode extension.

[1] https://github.com/elixir-lsp/elixir-ls

That’d be really handy! It’d make Phoenix that much more approachable. With good editor support, I much prefer macros to runtime OOP magic/design patterns.
That's awesome! Now, what needs to happen for this to be supported in generic LSP clients like Emacs lsp-mode/eglot or nvim-lsp?
Completely disagree with ecto[0] and plug. (I have some issues with ecto, but it has nothing to do with macros). The use of macros in those two libraries is parsimonious and exactly what you want in a DSL. For that matter ExUnit, too, has exactly the 'right' amount of macro usage. Phoenix, on the other hand, drives me up the wall.

I would not call Phoenix a "basic library". It's definitely a heavy framework. You can of course use "just Plug" (though very few people do).

Finally, I think the bigger problem is when you get devs that "take inspiration" from some of the way that Phoenix uses macros. It's a serious problem when a dev has built a macro that cleverly autogenerates a function call (so you can't search the codebase for that function to chase the call path) is not fun. This is the strategy that the Phoenix Routes (is that the right name? not to be confused with Router) uses. I hate it so much.

[0] as an ecto consumer. if you ever try to develop an adapter for ecto, you'll run into these problems. I think for example this is why it took forever for someone to build an ecto3 SQLite adapter.

Yes I never got into Rails for perhaps the same reason. There were tiny source files with some very succinct and clever code that obviously caused a huge amount to go on behind the scenes but it was all magic and impossible to trace or diagnose anything - for me.

Also discoverability simply wasn’t there in Rails and I feel the same with Phoenix. I have no idea what pieces might fit in anywhere. Having to know that you have to create a file with a particular name in a particular place if you want something to happen is a bit of a pain if you can’t find a solid reference on that, but also knowing that this is even a possibility - that a feature exists - means reading and re-reading huge documentation on every release just to have some idea of what might actually exist.

It’s very likely that I really just haven’t ‘clicked’ with the right way to work with either, but I fear that the only real way to do so is to become an expert - and that would be a huge time investment.

Still, I do enjoy looking at the code of projects using Phoenix, even if I can’t fathom how they knew to create certain files and what to put in them.

I'm about three years into using elixir professionally and I still kind of hate phoenix. There's a lot more to love in elixir than phoenix, and to be honest, especially if you're getting paid to work on a phoenix codebase, you can kind of grin and bear it, ask for help on slack/elixirforums, and it's mostly fine. There are truly only a handful of things where "you kind of have to know the magic incantations", and it's very easy to find someone who knows them. Often it's a generator. Typically you only need to be shown once per project at most (and everything else is a normal copy/paste away). And the debug lines making no sense is relatively rare. In the best sense of "being a framework", once you're past these problems, you're into pure elixir and all is good again.

Jose has stated that fixing internalized pain is a real priority for the elixir ecosystem moving forward, so it's entirely possible that these things will be fixed over time (or it's possible it won't!).

> Jose has stated that fixing internalized pain is a real priority for the elixir ecosystem moving forward, so it's entirely possible that these things will be fixed over time (or it's possible it won't!).

Ah, that sounds interesting. Do you have a link to where he said that?

I very rarely write macros but Elixir is much, much nicer to work with for having them.

Probably my favorite overall library in Elixir is Ecto. It's the go-to relational DB library and makes great use of macros for its query syntax DSL. The Phoenix router, controllers, etc also benefit quite a bit.

The thing that makes Elixir's macro implementation a really big win for me as a user is how explicit it is.

There is never a case of hidden magic. Just as you can see what functions are brought into a module by scrolling up and looking for the keywords "alias" and "import", you can find macros by looking for "use" and "using". This is very different from JavaScript, where another module could alter the prototype chain or overwrite attributes on any non-const object it has access to.