News for hackers. Stuff that matters.
Re:Elixir and Phoenix after two years by blunte on Monday April 05, 2021 @03:52PM (#26703772)
To add to the author's experience, I spent 18 months of production time with Elixir and Phoenix.

As he says, the templates are compiled and are blindingly fast compared to Rails.

Pattern matching is really really nice when used in the right places (and you'll miss it if you go back to Ruby); but it can be overused. There's a faction of Elixir folks who attempt to avoid all conditionals and instead seem to prefer multiple dispatch/multi-methods to handle different cases. That's nice and very concise, because then you can simply call a function and let the pattern matching resolve which of the various implementations you've defined handle it. The big downside here is, as a reader of the code, you have to basically mentally imagine what all cases are covered and what they mean. Sometimes simply reading a switch statement or if/else/then is much clearer.

The super special magic is in the Erlang VM. If you put more energy into learning it and its capabilities, and using it where appropriate, it can shape the structure of your greater system beyond just one webapp; and it can provide a lot of features without you having to cobble together many other (good but independent) solutions.

Lastly, single thread performance is basically a dog. In my anecdotal experience, the same external service written with Elixir+Ecto was 25-50% as performant as a Python+SQLAlchemy program. So the lesson there is, find ways to parallelize or otherwise scale your process if it is batch oriented and handling a large volume of data.

If you asked me today if I would prefer to use Elixir (and Phoenix) over Ruby and Rails, I would say yes... but honestly mostly just because it's a new fascination with different tradeoffs and a better functional story. Function is the past and the future, and it makes your life easier and simpler. Elixir as a language... borrowed too much from Ruby and has too much syntax. It is noisy in a Perl-like way, and perhaps there could be a more concise enhancement of Erlang which would get the job done and not have you spending time visually parsing code.

Re:Elixir and Phoenix after two years by astrowilson on Monday April 05, 2021 @04:28PM (#26704242)
> and perhaps there could be a more concise enhancement of Erlang which would get the job done

Erlang already is a more concise language than Elixir but also noisier as there are more punctuation characters. I would love if Erlang would drop some of its punctuation, as some of it is frankly unnecessary.

Elixir syntax is definitely simpler than Ruby’s. Probably in the same ballpark as Python complexity wise: Elixir has less keywords and less rules thanks to the macro system but on the other hand more affordances, such as optional parenthesis.

Re:Elixir and Phoenix after two years by dmitriid on Monday April 05, 2021 @05:44PM (#26705090)
> Erlang already is a more concise language than Elixir but also noisier as there are more punctuation characters.

What?

Here's more-or-less the entirety of Erlang's "noisier syntax with more punctuation characters"

  -spec f(A :: any(), B :: some_type(), C :: list()) -> any().
  f(A, {B}, [C | _]) ->
    A1 = fun() -> io:format(a, []) end,
    A1(),
    case C of
       <<1, _/binary>> -> 1;
       _ -> #person{ok = ok, field = C}
    end.
Edit:

  %% plus map syntax

  #{"tuple" => {1,2}}
  M#{"key" := "new_value"}

  %% plus list comprehensions

  [X || X <- List, X /= 10].

Elixir has all that plus more. The equivalent in Elixir is something along the lines of

  @spec f(a :: any(), b :: some_type(), c :: list()) :: any()
  def f(a, {b}, [c | _]) ->
    a1 = fun() -> IO.inspect(a) end,
    a1.(),
    case C do
       <<1, _ :: binary>> -> 1
       _ -> %Person{ok: :ok, field: c}
    end
  end

  ## and don't forget the capture and pipe syntax
  
  x |> (&f(y, {&1}, list)).()

  ## and default function parameters

  def f(a, b, c \\ []), do: something()

  ## and sigils

  String.replace(str, ~r".*", "")

  ## and Ecto adds its own characters

  id = 1

  (from p in Person, where: p.id == ^id, select: p) |> Repo.all()

  ## and dropping down to Erlang is a function call on an atom

  :io.format(x, y)

And I'm definitely forgetting more...

Edit:

  ## and string interpolation

  a = 1
  
  s = "The value is: #{a}"

  ## and two different map syntaxes

  %{a: :map, with: "various", keys: "as", atoms: 0}

  %{"another" => "map", "but" => "keys", "are" => "strings"}

  ## and different map access

  map[key]

  map.key

  ## and map update

  %{ map | key: "new_value" }

  ## and list comprehensions

  for x <- list, x != 10 do
  end
Re:Elixir and Phoenix after two years by literallyWTF on Monday April 05, 2021 @10:07PM (#26707207)
My god, it’s like Perl but somehow worse. How do you even keep track of what’s going on?
Re:Elixir and Phoenix after two years by astrowilson on Tuesday April 06, 2021 @02:06AM (#26708455)
That’s not a representative example of none of the languages. It is also just plain invalid for Elixir. I recommend checking actual code examples on their websites to form a better opinion.
Re:Elixir and Phoenix after two years by dmitriid on Tuesday April 06, 2021 @03:25AM (#26709048)
It's not that bad, really. As with most languages, syntax is the easy part.

From personal experience:

- You're comfortable with Erlang syntax within half a day. There's just so much less of it

- You're comfortable with Elixir syntax within a day of two of active coding. Function captures may trip you up once or twice more, but then it's fine.

Re:Elixir and Phoenix after two years by astrowilson on Tuesday April 06, 2021 @01:52AM (#26708362)
Your Elixir example is literally wrong. It won’t compile unless you do many changes. You are keeping -> instead do. You are still using commas after each expression. The fn syntax is wrong and also using parens for args. So you forgot to remove most of the punctuation noise.

You are conveniently not showing many examples where Elixir is considerably less noisier such as keywords, utf-8 binary strings, etc. Module definitions in Erlang use -(). while Elixir doesn’t use a single punctuation character. Erlang has special syntax for list and binary comprehensions while Elixir has unified both and uses no special syntax. Maps in Erlang also have two syntaxes, more ways of doing try/catch, etc.

If you take examples of actual code implemented in both languages, instead of cherry-picked invalid gibberish, you will find that Erlang has more punctuation per character than Elixir.

Re:Elixir and Phoenix after two years by dmitriid on Tuesday April 06, 2021 @02:43AM (#26708748)
> Your Elixir example is literally wrong. It won’t compile unless you do many changes.

I was typing this inside the HN comment box. And no, not many changes.

> So you forgot to remove most of the punctuation noise

For the two examples you could come up with for my entire text Elixir adds half a dozen new punctuation characters on top of Erlang.

> If you take examples of actual code

Of course. Here you go:

  Enum.sort(["some", "kind", "of", "monster"], &(byte_size(&1) < byte_size(&2)))
I mean, Erlang doesn't even have `&` as a part of syntax, but sure, "Erlang has more punctuation".

Besides, Erlang's syntax is significantly more consistent than Elixir's. Just take Elixir's two separate syntaxes for maps, for example. Or the dichotomy between syntax for functions and lambdas (both for declaration and invocation).

The only reason Elixir feels like it has less punctuation is that Erlang is actually more terse than Elixir.

Just having pipes and function captures everywhere makes Elixir code have more punctuation than Erlang.

Re:Elixir and Phoenix after two years by astrowilson on Tuesday April 06, 2021 @03:43AM (#26709172)
The example that you picked and wrote translates to this in Erlang:

    lists:sort(
      fun(X, Y) -> byte_size(X) < byte_size(Y) end,
      [<<"some">>, <<"kind">>, <<"of">>, <<"monster">>]
    ).
I will let others decide which one is noisier.

(And no, using Erlang strings here is not the same, especially when popular libraries like Hackney and Cowboy work only with binary strings)

> Erlang is actually more terse than Elixir

Which was my point since the beginning but you quoted only part of my reply. It is clear Elixir has more syntax than Erlang. The function definition delimiters (->/.) vs (do/end) is a good example in itself of how Erlang is more compact and using punctuation.

I stand with my position that if you take actual code, a file or a project, and write it in both idiomatic Erlang and Elixir, Erlang will have more punctuation per character and will feel noisier.

Here is an actual example. I got the hex decoding/encoding code recently committed to OTP in Erlang (https://pastebin.com/qMtj8mSY) and rewrote to Elixir (https://pastebin.com/zUCLeXZG). I ran them through the Whatsapp formatter and the Elixir formatter respectively. Both snippets compile and define proper modules. If you remove all whitespace, the rate of punctuation character per alphanumeric character for Erlang is 35%. For Elixir it is 25%. And FWIW, in this particular case, the Elixir implementation has roughly the same amount of characters as the Erlang one: 718 vs 721.

I will be glad to continue this discussion if you want to use actual code instead of fictional examples.

EDIT: I fixed an error in the Erlang snippet and updated the stats.

Re:Elixir and Phoenix after two years by astrowilson on Tuesday April 06, 2021 @05:12AM (#26709743)
I accept that my original sentence was unclear. I mentioned more punctuation characters in the context of conciseness, which obviously takes the amount of characters into account. Other than that, I don’t dispute Erlang has less syntax and I hope it is clear that Erlang is noisier (assuming the definition of noise is punctuation / character).

Regarding the compiler, most compilers have ambiguity. It is the reason why you have to put a space between = and binaries in Erlang. The question is if the compiler is going to pick a side or require the developer to be explicit. Modern compilers prefer to fail early in such cases, rather than letting a syntax error pop up later on.

Furthermore, the use of do/end vs do: is completely up to you. My original draft had only the first, which reduced the amount of punctuation in Elixir further, but I decided to include both styles because you will find both in practice. But if you want to stay consistent, you have the option.

Finally, happy to disagree on the “agglomeration of characters” in the Elixir example. The Elixir code has less punctuation and is clearer, despite the use of “do:” (which, as I said above, is optional).

Re:Elixir and Phoenix after two years by dmitriid on Tuesday April 06, 2021 @05:01AM (#26709664)
Let's go back to the original statement:

> Erlang already is a more concise language than Elixir but also noisier as there are more punctuation characters.

Nope. Elixir has more punctuation characters, and I've shown that.

Now, when we talk about "punctuation per characters of code", then yes, Erlang may have more punctuation in this regard. But it has another thing going for it: there's significantly less syntax in general, and it needs less brainpower to disambiguate (BTW, Elixir's syntax is ambiguous to the point that the compiler can't figure it out in certain contexts).

Even in the examples you provided:

  defp encode_hex(<<a::4, b::4, rest::binary>>, acc) do
    a = encode_hex_digit(a)
    b = encode_hex_digit(b)
    encode_hex(rest, <<acc::binary, a, b>>)
  end
 
  defp encode_hex_digit(char) when char <= 9, do: char + ?0
  defp encode_hex_digit(char) when char <= 15, do: char + ?a - 10
Oh, look, it was `f() do ... end` but then all of a sudden `f(), do: ` (with no end). Whereas Erlang's syntax is (mostly) the same forms everywhere.

And where Erlang is consistent due to terseness of syntax:

  decode_hex_char(Char) when Char >= $a, Char =< $f ->
    Char - $a + 10;
Elixir is an agglomeration of punctuation:

  defp decode_hex_char(char) when char in ?a..?f, do: char - ?a + 10
I have complained about Elixir syntax in the past [1] when I knew little to no Elixir (I'm now building a big-ish side project in it), but the complaint mostly remains.

Meanwhile Erlang may look like it uses more punctuation, but that punctuation can be much easier pattern-matched by our brains because it's consistent and means the same in (almost) all cases.

[1] https://medium.com/@dmitriid/in-which-i-complain-about-elixi...

Re:Elixir and Phoenix after two years by dnautics on Monday April 05, 2021 @08:13PM (#26706394)
you are conviently forgetting one thing:

The equivalent of "hello world" in erlang is

    <<"hello world">>
Not the best. Keyword lists in erlang are unpretty

    m:f(value, [{foo, <<"bar">>}, {baz, 47}])

Where in elixir you would do:

    :m.f(value, foo: "bar", baz: 47)
Re:Elixir and Phoenix after two years by dmitriid on Tuesday April 06, 2021 @02:45AM (#26708768)
> The equivalent of "hello world" in erlang is

is "hello world".

Edit:

I know that "" in Elixir is binaries which are <<>> in Erlang. In Erlang "hello world" is really a list of ["h", "e", "l", "l", ...].

What was surprising to me is how rarely that matters (unless you actually do a lot of text processing). And the functions that matter (like I/O) deal with io_lists anyway.

And since strings in Elixir are binaries, when you actually need to work with binaries, you're back to the same/similar syntax:

  <<0:1, 0:1, 1:1, 1:1>> == <<3:4>> %% Erlang

  <<0::1, 0::1, 1::1, 1::1>> == <<3::4>> ## Elixir


> Keyword lists in erlang are unpretty

Keyword lists in Elixir are nice, that's true.

Re:Elixir and Phoenix after two years by ksec on Monday April 05, 2021 @04:05PM (#26703932)
>Lastly, single thread performance is basically a dog.

Is that still the case? I thought BeamVM recently added JIT? I don't expect it to be LuaJIT or JS V8, but Ruby and Python Single Thread performance should be reasonable expectation?

I also wish some of these experience has more context in terms of code base size and team size. A Production environment of a small project with a team of 2 is very different to production environment of a project that is large and team of dozens if not hundreds.

Re:Elixir and Phoenix after two years by toast0 on Monday April 05, 2021 @04:42PM (#26704430)
I've seen several reports of pretty good performance improvements from the new JIT, but keep in mind a couple things:

It's included in the not yet finalized release that is currently only a release candidate. It's a little early to expect people to have experience with it. Some organizations might update quickly, but others will take quite some time. Also, the JIT isn't on all supported platforms; i think it's amd64 and aarch64 only at the moment, which probably covers most performance oriented servers, but maybe not everyone.

The other thing is it's not an optimizing JIT like Hotspot or v8; it 'only' turns the beam opcodes into native code as it's loaded. This eliminates interpretation overhead, but there's potential to optimize the native code in the future.

Re:Elixir and Phoenix after two years by blunte on Monday April 05, 2021 @04:11PM (#26704020)
In my case it was a team of 1 working on OTP 19 and 20. Since that is now 3+ years ago, it is quite possible things have improved in the single-thread performance area.
Re:Elixir and Phoenix after two years by onlyrealcuzzo on Monday April 05, 2021 @05:05PM (#26704727)
Elixir single thread performance /is/ much faster than Ruby and Python [1].

Are you sure you weren't just doing something sub-optimal with Ecto?

[1] https://amp.reddit.com/r/elixir/comments/46v1l1/is_elixir_fa...

Re:Elixir and Phoenix after two years by gregors on Monday April 05, 2021 @10:30PM (#26707354)
I'm a huge elixir fan, but in micro made up benchmarks I consistently see Ruby win over Elixir. I haven't run it with the jit.

One specific case that cripples Elixir - is as far as I know Elixir can't handle large multiplications efficiently. Ruby, Python use karatsuba or some derivative - from what I can tell multiplication in Erlang doesn't. Though I haven't actually asked anyone and was just poking around on my own.

example: Multiplying the first 100_000 numbers together I get

ruby multi.rb 3.47s user 1.91s system 99% cpu 5.396 total

elixir multi.exs 14.32s user 0.62s system 103% cpu 14.441 total

Re:Elixir and Phoenix after two years by strmpnk on Monday April 05, 2021 @11:09PM (#26707585)
I was curious to see what my JIT build looked like for me so I ran the following: https://gist.github.com/cararemixed/c232b888b9dbdaeb67967f35...

This shows a significantly different story between the two with at least similar code. Note I am not trying to optimize for the fastest factorial here as both can be made much quicker but roughly equivalent code seems to tell a different story for me.

(NOTE: I put the code in a module as iex interpreted expressions on the top level are always slower than compiled modules, even when pasting them into iex. Doing the same for Ruby didn't show any difference for me.)

Re:Elixir and Phoenix after two years by gregors on Monday April 05, 2021 @11:20PM (#26707633)
Neck in neck! That's amazing. Thanks for testing that out.
Re:Elixir and Phoenix after two years by strmpnk on Wednesday April 07, 2021 @08:48AM (#26724060)
I wouldn’t call twice as fast neck and neck (others seem to show that the old interpreter is neck and neck) but the primitive JIT is clearly pulling ahead here.
Re:Elixir and Phoenix after two years by astrowilson on Monday April 05, 2021 @04:34PM (#26704342)
The 3+ years ago is a good reference, thanks for sharing. The issue could also be Ecto related. Ecto 3, which might not have been out at the time, had a bunch of improvements on this front.
Re:Elixir and Phoenix after two years by cageface on Monday April 05, 2021 @09:37PM (#26707027)
The problem for a new technology like Phoenix that wants to dethrone an established player like Rails is that it's not good enough to be 50% better. It has to be 2x or 3x as productive to offset the smaller ecosystem and pool of developers. And I'm not convinced for most of the things that Rails is used for it's that big of a productivity boost.
Re:Elixir and Phoenix after two years by joelbluminator on Tuesday April 06, 2021 @08:08AM (#26710824)
> And I'm not convinced for most of the things that Rails is used for it's that big of a productivity boost

It could be a big productivity hit actually...

Re:Elixir and Phoenix after two years by joelbluminator on Tuesday April 06, 2021 @05:11AM (#26709733)
As a Rails dev I have to say: Why trying to dethrone Rails? I don't get the Elixir obsession with Ruby/Rails (yes Jose Valim came from Ruby - so what?). Ruby is smallish enough, I don't get the obsesssion with going after that segment.
Re:Elixir and Phoenix after two years by mercer on Tuesday April 06, 2021 @05:50AM (#26709982)
from my experience, Rails is not really much part of the general discussion in Elixir/Phoenix world. Sure, a number of us came from Rails, and lots of 'new to the community' discussions might hit on that, but that's a far cry from being 'obsessed' with dethroning Rails.

If anything, my impression is that a significant portion of the community still uses Rails for many of their applications, and some occasional grumblings aside, it's not a big deal.

Re:Elixir and Phoenix after two years by strzibny on Wednesday April 07, 2021 @07:58AM (#26723555)
Exactly!
Re:Elixir and Phoenix after two years by conradfr on Monday April 05, 2021 @04:17PM (#26704102)
> attempt to avoid all conditionals

I think that's a symptom of the lack of a return keyword (and therefore early returns).

Re:Elixir and Phoenix after two years by ch4s3 on Monday April 05, 2021 @04:55PM (#26704612)
> Sometimes simply reading a switch statement or if/else/then is much clearer

Case statements are pretty common, and for code I read the with statement[1] is even more common. This allows you to code a happy path broken into small steps and then collect your edge cases. It doesn't solve every case for if/else or cases, but it's a nice tool.

[1]https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1

Re:Elixir and Phoenix after two years by strzibny on Monday April 05, 2021 @05:07PM (#26704741)
I love with, completely forgot to mention it.
Re:Elixir and Phoenix after two years by vram22 on Monday April 05, 2021 @07:42PM (#26706136)
Don't know Elixir. Understand a bit about pattern matching from dabbling in OCaml and F#. Not clear about this:

>you have to basically mentally imagine what all cases are covered and what they mean

Why? Isn't the logic clearly specified in some way by the multiple dispatch/multi-methods? Because if not, how does the program work? So shouldn't a user be able to read the multi* code, and understand it, just like they can with if/else statements?

Re:Elixir and Phoenix after two years by dnautics on Monday April 05, 2021 @08:03PM (#26706296)
one difference is that you can really afford to be lazy with elixir. If your pattern match fails, one option is to let it throw a MatchError. Especially if the failure is rare, you just let the process die and your supervisors will restart the process into a sane state, and hopefully the next time you hit the code path it will be ok (for example: you are calling out to a 3rd party web service and they have a brief service outage, or some backhoe went over a network link and killed your packets).
Re:Elixir and Phoenix after two years by ch4s3 on Monday April 05, 2021 @04:00PM (#26703878)
> In my anecdotal experience, the same external service written with Elixir+Ecto was 25-50% as performant as a Python+SQLAlchemy program

I'd be interested to see an example here. Using Plug+Ecto has been roughly on par with most real world examples I've seen with SQLALchemy and Flask for single request performance. Python might be a bit faster for some workloads but once you start saturating the CPU, Elixir/BEAM really shines.

Re:Elixir and Phoenix after two years by blunte on Monday April 05, 2021 @04:08PM (#26703975)
I should have been clearer in my final note. My Elixir vs Python example was for standalone batch (script) processing. It was not in the context of a webapp.

The case was a recurring calculation system which would take a few hundred thousand records and do about a dozen different calculations against those (some calculations requiring additional lookups).

The original script was in Python, and then after the new webapp was built in Elixir, I rewrote it also in Elixir. I discovered that even tuning the batch sizes, the Elixir/Ecto version was much slower than the Python version. Then I looking into per-thread Elixir performance and found out that that is not its strong suit :). So the answer there of course is to leverage the strength of the tools and parallelize it; but I had no need to do that since I already had a working Python version. Could Elixir have been faster if multi-threaded? Probably. Single? No, I doubt it.

Re:Elixir and Phoenix after two years by ch4s3 on Monday April 05, 2021 @04:48PM (#26704512)
Ahh, ok. You've sort of hit a weird spot for Elixir. Python will just start up faster and that will be noticeable. Math is much better optimized in Python, though this is changing with NX.

The real gain here would probably be streaming chunks of rows out of ecto and maybe firing those off to tasks per CPU core. Which is easier than it sounds.

That said, you had working code so no real reason to rewrite it except as a learning exercise.

Re:Elixir and Phoenix after two years by toast0 on Monday April 05, 2021 @10:36PM (#26707382)
Typical ways to start beam end up doing a lot of work you might not need.

Taking some time to fiddle with the startup settings for a short running task can make a big difference. You might actually get better results by setting it to single cpu, because there's less setup that way. Really depends on how much total cpu time your work takes. If your VM is going to run for a year (or more), startup time hardly matters and so the defaults aren't highly tuned for a 30 second script.

Re:Elixir and Phoenix after two years by dnautics on Monday April 05, 2021 @04:11PM (#26704024)
Hm, if you have a chance to revisit it, you may want to consider Flow, which will likely take a lot of the pain of parallelization away.
Re:Elixir and Phoenix after two years by toolz on Monday April 05, 2021 @03:59PM (#26703860)
sqlalchemy is an ORM - I'm not sure it can be compared against a web framework, but my experience with phoenix vs python web frameworks is that phoenix is easily faster even for single-threaded web requests (which of course will utilize many threads for things like DB thread pooling etc.)
Re:Elixir and Phoenix after two years by TylerE on Monday April 05, 2021 @10:11PM (#26707227)
sqlalchemy contains an ORM.

You can do a ton with it without ever importing sqlalchemy.orm.

Re:Elixir and Phoenix after two years by toolz on Tuesday April 06, 2021 @09:05AM (#26711298)
Sure, but you're missing the whole point - it's not a web framework and it's never marketed itself as one to my knowledge.