Hacker News new | past | comments | ask | show | jobs | submit login
Thoughts on Elixir, Phoenix and LiveView after 18 months of commercial use (korban.net)
308 points by clessg on Aug 13, 2023 | hide | past | favorite | 130 comments



Hi @SupremumLimit, that was a great article, thanks for sharing. Here are some tiny remarks, in case they help:

* About dot when calling anonymous functions, I have just published an article I have been sitting on. I hope it clarifies a bit why it exists and its benefits for code readability: https://dashbit.co/blog/why-the-dot

* Regarding keywords, I actually don't consider them to be a replacement for named arguments. However, given they are called "keyword lists", it is an easy association to make. I will make sure we focus more on them as optional arguments.

* There is a distinction between `is_map` and `empty?` as it denotes which are allowed in guards and which ones are not. With a potential type system incoming, we may be able to enforce this too, making it easier for library authors to follow these guidelines.

* Your approach to using contexts is absolutely fine. Phoenix typically does not care how you answer the context question, as long as you answer it. The goal is to avoid the case where you build an application for 18 months without considering how it should be organized.

* You are 100% correct on functional components vs live components. I will make it clearer in the docs the former should be preferred.


I am not the OP, but I suspect the point about keywords vs map might be why was Keyword favoured over Map, i.e. why does `foo(a: 1)` compile as `foo([a: 1])` and not `foo(%{a: 1})`.

Nice writeup on why the dot <3


1. Elixir Keywords are Erlang's proplists plus syntactic sugar (making them easier to write and read), which in turn I guess were inspired by proplists in Lisp, but there they're lists of cons-cells, not a list of 2-tuples.

Also Erlang's proplists have a special case for Boolean flags, e.g. instead of [{key, true}] one can just write [key].

2. I think Phoenix Contexts are inspired by the DDD Bounded Contexts - a strategic pattern, which seems not be relevant for a typical web app built with Phoenix. But maybe it serves as a forcing function for the developer to think about the domain and how it may evolve in the future.

3. I surprised the author didn't complained about overuse of sigils in the language ;)


Thanks for the post about the dot, interesting stuff and the TL;DR is excellent.


> With the context above, I had to answer the following question when designing *Elixiur*: should anonymous functions have a dot when invoked or not?


Fixed, thanks!


Very solid article with many points I agree with.

Some notes:

Keywords are not simply "another pragmatic solution that won over finding a more elegant way to support named arguments" and they are inherently different than maps and serve different purposes. Maps are unordered hashes, keyword lists are constant time, ordered list that can have duplicate keys. For example, in the language core, duplicate keys are used:

    import Foo, only: [bar: 1, bar: 2]
Ecto's query syntax makes use of both duplicate keys and ordering:

    from q in query,
      join: t in assoc(q, :table),
      on: q.table_id = t.id,
      join: o in assoc(t, :other),
      on: t.other_id = o.id
Everyone complains about the f.() syntax. It's necessary as explained in the guides in the Anonymous Functions section [0]. There is also a nice little parallel to Erlang in that a regular function call looks like `f()` whereas an anonymous function call looks like `F()`.

Contexts can be confusing, unfortunately. They're based off of bounded contexts from Domain Driven Design. I wouldn't say there is one right way to use Phoenix contexts, though reading up on DDD's bounded contexts really helps. I already had DDD experience before I learned Elixir, though, so it's easy for me to say and maybe more of a pain for others who don't want to do that.

Comprehensions can be much more concise in certain situations. You can certainly get away with avoiding them, but they can really come in handy sometimes. Not gonna give examples as this is getting too long. A short example would be Cartesian products.

Finally, I don't quite understand the gripe with `on_mount` or even `live_session` but that's been addressed in another comment.

[0] https://elixir-lang.org/getting-started/basic-types.html


> Contexts can be confusing, unfortunately. They're based off of bounded contexts from Domain Driven Design. I wouldn't say there is one right way to use Phoenix contexts, though reading up on DDD's bounded contexts really helps. I already had DDD experience before I learned Elixir, though, so it's easy for me to say and maybe more of a pain for others who don't want to do that.

Jenny Shih gave a talk comparing Phoenix contexts with DDD bounded contexts.

https://www.youtube.com/watch?v=vr-qhHrN5_4


Another note:

The “inconsistent” naming is actually a way to distinguish between guard and non-guard functions: is_map can be used in guards while empty? cannot.


> keyword lists are constant time, ordered list

Unless there’s some kind of behind-the-scenes magic that I’m unaware of, keyword list look up is O(n), not O(1).


Oops yes, typo sorry, meant non-constant.


No, you’re exactly right, they are just regular singly-linked lists of tuples, nothing more.


[I am the author of the post.] I know that keyword lists are different from maps, but I think their superficial similarity to maps isn’t ideal, and I don’t think that they’re the best possible solution (eg consider pattern matching on keyword lists which is order dependent, it’s not intuitive in the case of named args). They’re confusing to newcomers because of these things.


> They’re confusing to newcomers because of these things.

Then they learn how they work and stop being confused.

I struggle to see why we are so terribly afraid of people learning. Programming is not an innate instinct after all.

A programmer that doesn't understand linked lists yet is basically a newbie, but it's a topic you learn in one day and makes you a better programmer.

This should be encouraged, rather than listed as a con.


Both can happen at the same time, they can get confused and be learning.

It would be best if there was no confusion whatsoever, but given that it is most likely impossible (in any language), it is important to learn from where the confusion comes from, so we can continue improving the docs. It is a positive loop to have, as long as we listen. :)


The main problem with them in Elixir is that you can't pattern match on keyword arguments without imposing an arbitrary order.


--DELETED--

I said suggested you could pattern match on keyword lists with guards but that isn't true—I should not respond to things first thing in the morning.


I mean pattern matching in the sense of binding a variable to each option.


Of course, they're linked list, they have an inherent order. Once you understand how they behave, you understand where the abstraction leaks.


Yes, I understand why this is the case. However, it is still a mild annoyance that you cant (sensibly) write e.g.

    def foo(x, opt1: opt1, opt2: opt2)
If optional arguments were first-class citizens in the language, then you'd presumably be able to use patterns along these lines.


It is unfortunately much trickier because keyword lists are also optional. So what happens if opt2 is not given? We could raise but... since they are optional, we wouldn't want it to raise but rather have a default value.

Of course, we could then say "let's allow a default value to be given when pattern matching keyword lists". The problem is that no other pattern works like this and it would be inconsistent.

I think optional arguments are fundamentally incompatible with pattern matching (in Elixir). I understand why we would want that but, if we did have it, we would be adding tons of complexity and it would stand out as a sore thumb. I am also wary of going towards the same direction as Ruby and Python which have quite complex argument handling (albeit we have slightly different feature sets).


I think there is a possible design for them that is not too bad, but it would need named parameters. I think the time o encountered that idea was part of a longer post about solving a different problem in Rust.

But i doubt it would be an urgent thing to change.

Tldr: add named arguments (optional). You can call with anon names, in which case it is positional as it is today. If you use named arguments to call, you have to use them in the order they have been declared, just like with positional, but you _can_ omit some. These get the default value.

There are ofc combinations that could be allowed, like anon until the first optional one, whatever.

It was part of this post, but don't it was a solution to a different problem kinda (and kinda not technically) https://faultlore.com/blah/defaults-affect-inference/


I get that. But it's built on top of the Erlang VM, neither Erlang nor it's VM support these. All languages have their own leaky abstractions, and stuff that should in theory work, but doesn't.


Keyword lists were certainly confusing as a newcomer. But, FWIW, they existed well before maps. The AST is made up of only tuples and keyword lists on top of atoms and other literals. (Actually, if I'm not mistaken, keyword lists themselves are just lists with tuples of atom/value pairs. If you dig far enough down, it's incredible just how much of the language is built on top of a tiny number of primitives. Almost every bit of syntax you can think of is probably several layers of macros.)


Keyword lists are from Erlang's history really. Erlang didn't have maps or records for a long time and used keyword lists to perform a similar function.


> Actually, if I'm not mistaken, keyword lists themselves are just lists with tuples of atom/value pairs.

You are correct. A keyword list is `[{atom, any}]`.


Ah yes, sorry, you did say you understood them in the article but wasn't obvious you understood cases where one was better suited than the other.

Otherwise very nice article and sorry I only focused on the rebuttal points! I absolutely agree about grouped alias/import/require. I know those weren't always in the language and I always assumed it was a community request (but maybe not). I personally prefer to avoid `alias` and `import` whenever possible! There a handful of scenarios where I use them but I love how Elixir is big on locality. Any jumps I can avoid, even to the top of a file, is a win in my book.

Otherwise the focus on pragmatism is apt. Possibly my favourite thing about Erlang is that it is the only currently widely adopted general purpose language that was built to solve an actual business problem, and that problem so closely resembles web programming. All of the language decisions were made in support of their business goal as opposed to trying to come up with a beautiful, academically sound, language. Something about that really resonates with me so I kinda enjoy and embrace the "warts". Call it Stockholm Syndrome if you must :)


I’d also like to add that the Access module provides generic access to key-value collections just like Enum does for sequences.


Maps can be ordered though. In Python they are, and keyword arguments in Python are implemented as these ordered maps (dicts), and they disallow duplicate keys. So I do understand how it can be confusing coming from another language for example.


It's just a matter of algorithmic complexity. A hashmap, ordered or not, is more complicated than a linked list, which is what Keywords are built upon.

The former requires taking an arbitrary piece of data, hashing it to an integer, and indexing a cell based off this value, dealing with potential collisions on the way. In C terms it's 50 lines long.

The latter is a simple iteration and pointer equality comparison (due to keywords being interned strings). In C terms it's 5 lines long.

It is confusing if you've never programmed close to the metal and only know high level languages. Maps are good for data that you need to access in random order, linked lists/keyword lists for data that either you access in linear order, or it is so small the overhead of a map is too much.

If you have less than 10 elements, a linked list will always be faster than any hashmap on modern CPUs.


> It's just a matter of algorithmic complexity. A hashmap, ordered or not, is more complicated than a linked list, which is what Keywords are built upon.

You mean implementation complexity right? Anyway, I was talking about semantics, not performance or implementation.

> If you have less than 10 elements, a linked list will always be faster than any hashmap on modern CPUs.

Citation needed. That personally sounds totally bonkers to me. Faster HOW? If the pointers of each node in the linked list are far apart in memory the L1/L2 cache misses will absolutely destroy the performance.

I think you're talking from a ton of assumptions that you haven't stated. If the list is that short, why isn't it a contiguous array in memory? That's going to be way smaller and way way faster in every way.


Ordered maps are usually a hybrid linked list. When iterating you can traverse the linked list data, rather than the buckets.


> Live components are best avoided if possible, in my view. The documentation is quite bullish on them, presenting them as just another thing alongside functional components, but I think they should be more of a last resort, with functional components used whenever possible. This is because live components immediately raise questions about what is the source of truth for the state (view or component), they are harder to test given that they are stateful, and they have some warts like inconsistency in how messages have to be passed depending on whether the live component is a child of a view or another live component (although I know this is on the roadmap to be fixed).

This has been the single biggest issue we continue to face in organizing our components. The way to communicate between live views, live components, etc, still feels inconsistent, incomplete, and leaks abstractions everywhere. In some places, between handle_event (which is sort of a live_view thing), handle_info (which is a genserver thing), send (elixir/erlang thing) and send_update (which is kind of a live view thing, but requires a DOM ID, and you handle it via "update", not via handle_*), we end up having tight couplings in code, not fully understanding when to use which one, or having to pass a bunch of extra attributes that only send_update would want (module, DOM ID, etc).

Given that PubSub is baked in, it seems like it could be a really powerful abstraction for all the message passing between components, with elegant namespacing between components, their parents, etc. You would just handle_event() to a pubsub topic and stop caring about who in the child hierarchy threw it.

Somehow coming up with an elegant event abstraction that combines send_update, send, etc would take live_view to the next level as far as maintaining larger groups of components and their interactions.


> or inconsistent naming (is_map vs empty?).

fyi, this isn’t inconsistent. https://hexdocs.pm/elixir/1.15/naming-conventions.html


While true, I think the larger problem is that it is not intuitive (at least to me) when I can expect a function to be of the `is_` vs `?` form. It's a leaky abstraction.


The only time `is_` is used, is with functions permitted in guards. These are functions defined in Erlang, and only a small handful exist, and you learn them very early on. With the advent of `defguard`, it is conventional to use `is_` with custom guards as well, but that’s the intuition - guards vs general predicates.


Sure thing. My point is that, if I want to check whether an argument is a keyword list, I have to do extra mental work to guess whether the correct function to use is `is_keyword` or `keyword?`. There also doesn't seem to be a consistent rule I can apply to figure out whether it's one or the other. Conversely, I also get tripped up every time I want to add a guard to a function like, "is the thing I want to test written as a macro or not?".

I understand the reasoning for the distinction and its roots in Erlang, it's just not very elegant to work with.


If you’re having to do extra mental work to guess, it means you don’t have enough familiarity with the language, its type checks/guards, and the standard library. That’s not the language’s fault.

If you want to check the type of a thing, you always want the matching `is_<type>`. It can be used anywhere in your code, including guards. There’s no guesswork involved here. That is the consistent rule.

When you see a function with a `?`, look at the typespec and the function name—give it the argument(s) it expects and it will answer the question on the tin with a boolean. Again, there’s no guesswork. These functions can be used anywhere except guards—that is the consistent rule for boolean functions.

> Is the thing I want to test written as a macro or not?

I have never had to ask myself this question, and I struggle to parse it. Is the “thing” you want to test referring to the value or to the test you wish to perform on that value? Since you’re asking about macros, I’m assuming you mean the guard test. For that, just learn what guards are available[0] (you can also write your own :D ).

[0]: https://hexdocs.pm/elixir/1.15.4/Kernel.html#guards


> If you’re having to do extra mental work to guess, .... That’s not the language’s fault.

Depends. In this case there are definite rule in place which you can learn. Overall, that's definitely language's fault. On other note, Elixir actually very good at consistent naming

- standard library is designed, not meshed up and grew layer by layer like js/php abomination (erlang one is not consistent and it leaks sometimes)

- Pipe operator by its mere presence enforces correct order of arguments, even in 3rd party code


It is not a tool’s fault that, upon providing the relevant material that describes its usage and helps a user learn how to use it, the user ignores that and then blames the tool for not working how they want it to work.


*Assuming naming rules are consistent. After spending 5 years on php, i've never learned its function names and order of arguments. Even daily stuff like `strpos` requires looking at documentation, dozens time per day


The larger problem is not reading the docs to understand and learn language conventions. If one reads the docs, it is immediately clear that the `is_` prefix denotes a function that returns a boolean and can be used in guards. The `?` form is for all other functions that return a boolean. Which means one will gain an intuitive sense of every function you see in the `is_` and `?` forms.

It’d be immensely more helpful and productive if coming into a language meant one would spend the time to read, learn, and understand that language’s conventions and standard library—and read the source code for it! It pays serious and continuous dividends, and is the fastest way to gain an intuitive sense for the language.


There's no need to be rude. My point is that it is not obvious, given some arbitrary value and some property to test for, whether that property can be expressed using a guard or not.


I was not being rude.

What you say is not obvious is, to me, a result of not reading the docs, guides, and source code of the language and its standard library. I have seen this pattern repeatedly—those engineers who have learned the language find these things obvious. It’s not an insult, but a push to fill in those gaps. Elixir and Phoenix both have some of the best documentation you will find.


I disagree. I've learned a ton of programming languages over the years and the big differentiator between 'welcoming' and 'gatekeeping' communities around those languages is that the welcoming ones go out of their way to make new people welcome and the gatekeeping ones go out of their way to make the barrier to entry as high as they can. Telling people (several times, actually) in this thread that they should go read the documentation first is rude to the point of being on a different level than just gatekeeping. It makes the assumption that (a) the person didn't read the docs (b) that if they read them that they're too stupid to understand them and (c) that the documentation is perfect and explains things in a way that connects to the persons' level of understanding. None of these may be the case and to assume them is really bad. Consider that you are an ambassador for Elixir in this context and that you are driving others away, which does not serve Elixir and ultimately doesn't serve you.

It also stands in sharp contrast to how others in this thread have responded, which I think could easily be used as an example for the 'welcoming' element. Now, it is possible that you are for some reason personally irritated at 'newbies asking stupid questions' but consider that once upon a time you too were a newbie and that there are many fields where you are a newbie today and where talking to people and asking questions will serve as an accelerator to gaining knowledge because interaction is a much faster path to clearing things up than staring at the documentation.

Whether Elixir and Phoenix have 'some of the best documentation you will find' is besides the point, that only shifts the line at which interaction with others becomes the main driver of further advancement. And that good documentation wasn't written to serve as a hook for a putdown for new learners of the language.


Let's burn some karma to point out the toxicity found in many individuals from the functional language crowd.

> I was not being rude.

Translation: the person asking needs to RTFM because they have a problem with something that is obvious or I don't understand and it doesn't matter which. This isn't being rude.

> It’d be immensely more helpful and productive if coming into a language meant one would spend the time to read, learn, and understand that language’s conventions and standard library and read the source code for it!

Translation: the person asking is wasting time, being unhelpful and unproductive asking questions

> I still have enough of it learned because that is my job. It’s what we get paid well to do.

Translation: the person asking must not be doing their job because they don't understand things the way I do (I'm a software perfectionist, ofc)

I believe The Erlang (and through extension, Elixir) community seems to engender and defend this kind of back-handed approach to "helping". It doesn't just come off as elitist, it is often little more than taunting. Suggesting that someone pours over documentation (and laughably source code) to explain patterns (or a lack thereof) in a highly abstracted language is counter-productive.

For the record, I'll just hide your posts from now on as you cannot seem to help yourself bob.


I'm not here to pick a side. I'm sure all of us can be more friendly to each other.

However, I'm a bit curious about when you say

> Suggesting that someone pours over documentation (and laughably source code) to explain patterns (or a lack thereof) in a highly abstracted language is counter-productive.

Isn't that what documentation is for? To learn about whatever's being documented. What would you suggest would be the better way to convey this information?

For source code, I can see your point. Although in my experience source code has the benefit that you can be certain that it doesn't lie. It does exactly what it says. Sometimes this can be a really nice benefit when trying to figure out what's going on. Regardless of the language or environment you are in.


> Isn't that what documentation is for?

I would agree, if everything was documented. The issue at-hand is what is not explicitly documented. What constitutes a guard-worthy function? ie A pure javadoc without any notation about what the API does, is not sufficient.


> The issue at-hand is what is not explicitly documented. What constitutes a guard-worthy function?

https://hexdocs.pm/elixir/1.15/patterns-and-guards.html#guar...

Plus the docs lists on the sidebar which custom functions are guards. Examples: https://hexdocs.pm/elixir/1.15/Integer.html#guards


The question was about why not what is allowed as a guard. Perhaps this was not clear amongst the noise.


You are reading an incredible amount of hostility into my comments that is not there.

> Translation: the person asking needs to RTFM because they have a problem with something that is obvious or I don't understand and it doesn't matter which. This isn't being rude.

That’s an incredibly uncharitable translation and doesn’t accurately match my meaning or intent. At each point in this thread, other commenters and I have provided explanations and linked documentation. Each response has been of the form “sure thing / that’s true, but it’s not obvious to me”. Then a repetition of the core complaint. This has led to more attempts at explanation and linking documentation, just to get the same response. Saying RTFM is one thing, but it’s simply not rude to suggest reading docs would bring clarity (and suggesting reading docs would help is not the same as answering “RTFM”).

> Translation: the person asking is wasting time, being unhelpful and unproductive asking questions

Not at all. I haven’t once felt like the person asking was wasting time, being unhelpful, or being unproductive asking questions. My comment was simply saying that it is immensely more helpful and productive [to a person entering Elixir for the first time] to read Elixir docs and guides and, where accessible, the language/stdlib source (it’s all Elixir), because it pays serious and continuous dividends on the journey of mastering the language—compared to jumping in deep before you’ve built an intuitive understanding of how it works. It wasn’t a slight or an insult.

It was the same advice I received early in my career in a completely different language that has proven itself true with every language I’ve learned since.

> Translation: the person asking must not be doing their job because they don't understand things the way I do (I'm a software perfectionist, ofc)

Amusing weaponization of my HN profile, but your translation is still unfair. Complaining about memorizing a handful of guard functions felt to me like complaining about doing the job. I don’t expect anyone to understand things the way I do. Nor was I trying to backhandedly insult or taunt.

I spend nearly half my working hours each week reviewing thousands of lines of Elixir code from hundreds of engineers, pairing with 10s of them, helping people learn, explaining how things work, and learning new things myself alongside them every day. Every time anyone doesn’t understand something, it’s an opportunity for us to pair up, dig in, review docs, come up with new explanations, show off code to explain and cement concepts, etc. That’s not the same thing as complaining and labeling something as a language problem, have multiple people trying to help explain it, but keep saying “but it’s not obvious and doesn’t match my expectations, so that’s Language X’s fault.”


It's been quite a while since I saw such a mismatch/meltdown on HN like this. Don't take it too personally. I did think your comment (the one about read the docs & some source code) was a little dismissive, but in context of the rest of the thread it makes some sense. It does annoy me greatly when people spread the same thing across 10 different comment branches, ignoring answers they got in other branches, so I very much sympathize with you. In fairness HN obscures the timestamps after an hour, so it can be really hard to figure out the order of comments when reading later. Biggest HN feature request: I would love to be able to see the exact stamp (to the minute at least).

I think you did a fine job, and I also commend you for having thicker skin and not responding to the personal attacks on you in-kind.


> but it’s simply not rude to suggest reading docs would bring clarity

It's pretty rude to assume/insinuate that I haven't read the documentation just because you misunderstood my initial concern.


Then explain to me, purely by referencing the Elixir documentation, why URI.char_reserved?/1 is not instead called URI.is_char_reserved/1 such that I can use it in a guard.

There's nothing intuitive about memorizing dozens of built-in functions that are (for opaque reasons) different from all the other built-in functions.


Guards are very small, very fast functions built in the BEAM VM.

is_integer is a guard (in pseudocode: cell.type == TYPE_INTEGER).

URI.char_reserved? is too high level to be builtin the VM.

Same for the hypothetical is_keyword_list mentioned above. How would you know if something is a keyword list?

1. It is a list

2. The first element is a tuple of 2 elements

3. The first element of the first tuple is a keyword

4. Every other element in the list obeys #2 and #3

What if you pass a list with a million elements to a guard like that? The VM would grind to a halt traversing every single element to make sure the whole thing is a Keyword. This is why there is no such guard.

So sure, you might have to consult the docs for what's a guard and what is not, but can also be understood intuitively.


Your point is rendered moot by the fact that `in` is allowed in guards. See my other response as well.


That's a fair observation. Perhaps the exception that proves the rule.


> Then explain to me, … why URI.char_reserved?/1 is not instead called URI.is_char_reserved/1 such that I can use it in a guard.

Because it is not meant to be a guard—it cannot further empower a function head and pattern match and be optimized and/or inlined by the compiler. Simple as that.

    Not all expressions are allowed in guard clauses, but only a handful of them. This is a deliberate choice. This way, Elixir (and Erlang) can make sure that nothing bad happens while executing guards and no mutations happen anywhere. It also allows the compiler to optimize the code related to guards efficiently.[0]
> There's nothing intuitive about memorizing dozens of built-in functions that are (for opaque reasons) different from all the other built-in functions.

I’m sorry, but I’m now struggling to believe you’re discussing this in good faith. Opaque reasons? I see none. It sounds like you either do not like or understand the reasons. It also sounds like you have an expectation that the existence of a boolean function means you can use it in a guard. But that’s your expectations not matching the language’s features and reasoning, neither of which are opaque. There’s nothing intuitive about memorizing dozens of functions? Assuming you’re a software engineer, that is your job. I probably have hundreds of functions memorized, across dozens of standard library modules, in multiple languages. Even if I don’t recall what specific options or arity a function has, I still have enough of it learned because that is my job. It’s what we get paid well to do.

[0]: https://hexdocs.pm/elixir/main/patterns-and-guards.html#guar...


> Because it is not meant to be a guard—it cannot further empower a function head and pattern match and be optimized and/or inlined by the compiler. Simple as that.

First of all, that's not an explanation, that is handwaving. The real explanation is that URI.char_reserved? could be rewritten using defguard, because it only uses `in`[0]. An arbitrary choice (whether conscious or unconscious) was taken, that this particular standard library function is not allowed to be used in guards. But there is no good reason for it.

Secondly, are you claiming that it is not useful to be able to use URI.char_reserved?/1 in a guard? That's obviously bullshit.

Finally: The real reason why some things can be used in guards and other can't, is that Elixir must be able to guarantee that no side-effects happen while evaluating a guard[1]. This is a good reason (a good follow-up question is, "why must guards be side-effect free?") and something you can use to form a mental model of which functions you can use in guards and which you cannot, but it is not described anywhere in the Elixir documentation. It's not an easy thing to form a mental model around, but it can be done.

To be fair, I think this is a shortcoming in Erlang as well. It would be better to be able to look at the type of a function and be able to tell whether I can use it in a guard or not. Or just allow all functions to be used in guards, such as in Haskell.

0: https://gist.github.com/Munksgaard/ccac61310651d3402571506e4...

1: https://www.erlang.org/docs/22/reference_manual/expressions#...


I have read this whole subthread and it is quite interesting because it is clear everyone involved knows what they are talking about, but speaking past each other. :)

I agree with you that you have to build a mental model and you have to memorize (or consult) which guards are available, but arguably the `is_foo` vs `foo?` distinction here is a positive one, because it only takes a glance to know if it is a guard or not (and the majority of guards have the `is_*` prefix so you have to memorize fewer).

You are correct URI.char_reserved? could have been a guard - but that should not be a confusion point for a user of the library - it is clear it is not a guard function. The confusion only arises as a contributor once you read the source as to why a certain choice was made (in this particular case, char_reserved? was written before defguard existed, but even if I wrote it today I am not sure I would consider the possibility users would invoke it in guards).

Btw, I know you know this. I just hope this clarifies the whole discussion a bit for others!


The goalposts of this conversation seem to keep moving. It sounds like you don’t like some of Elixir’s choices. That’s fair.

I wasn’t hand waving, I was simply summarizing guards in general. We’ve both provided the same explanation regarding why some things can or cannot be guards —you’ve linked to Erlang’s discussion of guards; I’ve linked to Elixir’s. So we both understand guards and their limitations.

I’m not terribly interested in arguing about what should or shouldn’t be “guard-worthy”. The built-in guards don’t strike me as arbitrary choices, but as intentional choices as part of designing core language features—that’s all in Kernel, on which everything else depends.

The URI module, while a great part of the stdlib, is not core language guard material. I can say that in 7 years, I’ve never needed or wanted a built-in guard to check if a character was a reserved URI char—but I can believe some problem spaces might have use for enhanced guards. Why, I’ve written my own for having more readable and shorter guards for things like empty lists, maps, and more. The ability to compose those from core language guards and features is one of the many things that makes Elixir a great language for my use cases.


As a new Phoenix convert I agree with a lot of these points from my experience too. The functional components make more sense in the majority of cases (and I think the docs point this out) and having the duplicate auth logic with on_mount feels less than ideal.

Hopefully the team will see this and make it even better!


I find a lot of engineers I work with don’t understand on_mount. You shouldn’t have duplicate logic in on_mount—if you do, that code should be in a module. You only have the logic once, but you may call the function twice (though you don’t have to).

All your auth logic (and any other bits you need to set while starting up request state) should be a function in a module somewhere that returns expected values. Then, you just care to set these values to Conn and Socket assigns appropriately, keeping in mind that Conns and Sockets aren’t the same—when you have one, you don’t have the other. Thus, on_mount to the rescue:

1. Use Plug pipeline(s) to call your module function to compute and add bits to Conn.assigns (and you can add those values to the session to avoid computing again, if you’d like).

2. Use on_mount to assign_new:

2a. assign_new pulls from Conn.assigns by key for the dead render while you have a Conn.

2b. The fallback function gets that value again when you no longer have a Conn (recompute or pull from session if already there).

Your actual logic should only exist in one place. And there are easy strategies you can use to avoid expensive computations if necessary.

[Edit]: Not suggesting you don’t understand it, just to be clear. I’ve just experienced a lot of coworkers fighting against, abusing, and misunderstanding Conns, Sockets, and assigns.


Exactly.

Since mix phx.gen.auth handles the authN side, it’s really just authZ that will be idiosyncratic to your application logic.

In every authorization system I’ve written in a Phoenix app (which 8 now), I’ve taken this approach above.

Make a pipeline of rules that take a conn or socket and authorize or not based on what’s in the assigns. Better yet, at the very top of the pipleline, wrap the conn or socket with an auth struct that holds metadata like authorization status and any redirection to be done. Unwrap that struct into the conn or the socket at the end of the pipeline, where the status is resolved.

This way, every request can be default unauthorized and you can apply complex, composable rules for granting permission to take various actions on resources.


In other words phx.gen.auth generates a lot of code that may or may not in part suits your authentification needs and Phoenix gives you nothing for Authorization ;)

(I am a bit bitter after implementing authentication with Firebase and authorization in a Phoenix app? Maybe ahah).


This a bit too negative of an interpretation.

In 100% of the apps where I've used mix phx.gen.auth, the code it's generated has been suitable. In some cases, I've used it in conjunction with a library like Ueberauth for social logins, but it's been strictly superior to older workflows using 3rd party services or frameworks that take over the whole user table.

Reaching for something like Firebase or especially Auth0 has added effort in the long run in each project where I've inherited that decision. The typical end-state seems to be a soup of logic split between the 3rd party provider and inside the application. It's more difficult to reason about and more expensive to audit.

Nothing is going to do your authZ for you, unless it was made with your business logic in mind. Different apps are going to have radically different needs and there isn't a single best solution for all of them.


> This a bit too negative of an interpretation.

Yes :)

> 3rd party services or frameworks that take over the whole user table.

I've never used a Framework when the auth library can't just use your user schema/entity.

I will never agree that using a generator and committing files I didn't write is better DX than using a library and its documentation (which will be easily updatable). It's also kind of limited in scope. Authentication is more than that. Like, JWTs for example.

To each their own.

> Nothing is going to do your authZ for you

The business logic no, the rest yes.

How do I limit a controller or an action for admin users in Symfony ? #[IsGranted('ROLE_ADMIN')], or for a specific user? #[IsGranted('edit', 'post')], done. And the auth/auth are available anywhere in the framework.

How do I do that in Phoenix ? Pull Bodyguard, write a bunch of "with", plugs, or scope with additional code I have to write and maintain.


> How do I do that in Phoenix ?

Assuming you have a `current_user` stored in assigns, I implement basic role access as:

    def edit(conn, param) when conn.assigns.current_user.role == :admin do
      ...
    end
If that's too long:

    defguard is_granted(conn, role) when conn.assigns.current_user.role == role

    def edit(conn, param) when is_granted(conn, :admin) do
      ...
    end
It also works on LiveViews with guards on handle_event.

---

The other way of doing authorization, which IIRC is similar to voters in Symfony, I typically implement with protocols:

    defprotocol Authz do
      def can?(resource, action, user)
    end
Then in my Post schema:

    defmodule MyApp.Blog.Post do
      schema "posts" do
        # ...
      end

      defimpl Authz do
        def can?(post, :view, user) do
          post.public or can?(post, :edit, user)
        end

        def can?(post, :edit, user) do
          post.owner_id == user.id
        end
      end
    end
You may encapsulate in a controller helper like this, although you cannot use it in guards:

    def can?(conn, resource, action) do
      Authz.can?(resource, action, conn.assigns.current_user)
    end
Now you can call it to check against any resource whatsoever. If you don't implement it for a resource or for an action, it will crash as expected.

I find it requires less boilerplate than the Voter approach in Symfony (but it has been quite some time since I last checked it). No additional abstractions either. The only downside is that it doesn't work annotation/@decorator style (but if you really want it, it should be doable).

---

However, my favorite way of doing authz is by scoping the queries. Typically all of my context functions receive either the org, the user, or a "session" data structure with both which I use as the starting point of my queries. Then I complement with Authz.can? style when that's not enough.


That's sensible ways of doing things obviously. The can? approach is very similar to the Canada lib IIRC. I'll probably try some of those.

But the best non-business code I like is the one I don't have to code, commit and maintain.

You don't need voters for the example I wrote earlier though, in fact I don't remember the last time I had to write one.

But I think it's more a functional vs OO discussion and we know how those goes ;)


I can see how #[IsGranted(role)] would work without voters but how would #[IsGranted(post, view)] work without voters? Would it dispatch to the post object? If so, then it is pretty much the same as protocols, yeah.

Follow up question: do you think the above would be helpful as a short-guide on Phoenix?


Yes you're probably right about the voter, I've probably mixed it up with the automatic parameter conversion for routing.

I've done too much Phoenix lately I forgot the rest ;)

About the guide I don't know, maybe a blog post yeah, but there is libs that do all that, maybe there's even too many of them. They are fine but lack the cohesiveness you get with something integrated in a framework.


can you provide sample code of one of your auth you have created, I would like to learn how to code the pipelines as your mention

thanks a bunch


Do you mind briefly expanding on your pipeline process?


A possible counter point to the dislike of comprehension, depending on why you don't like them:

https://www.mitchellhanberg.com/the-comprehensive-guide-to-e...

I discovered a lot of things in this post that made me see them as much more valuable than I'd originally thought.


Bitstring parsing examples using Elixir comprehensions always seem so much nicer than anything I’ve seen anywhere else. I need to play with Nerves to have an excuse to learn them properly!


I recently wrote a binary protocol in C and Elixir. The Elixir implementation was so much simpler and 1/3 of the code, and has better error messages


That’s not open source by any chance is it? I’d be really curious to see the comparison!


Its for our proprietary product, but maybe I’ll consider writing a blog post on how they compare


I'd encourage the author, and anyone else using Phoenix and liveview, to check out Surface

https://surface-ui.org/documentation

It basically brings some conveniences from Vue/react to live view components, and is wonderful


[I am the author of the post.] I have! Didn’t quite see enough value and for me something like Petal makes more sense.


Can it handle events driven within table components managed through tabbed navigation?


Yup, there's even examples of such in the docs


this? https://surface-ui.org/samplecomponents/Tabs

I'm still skeptical until I see the backend to this. This exact scenario is causing me grief with vanilla phoenix liveview and components.


Good article. My experience using it to do an API endpoint system to a proprietary MQTT system has been really enjoyable. I really like the Elixir community. The slack channel is really helpful. The documentation is pretty good. And I like the fact that I could solve my problems with out needing a huge tone of micro packages that I have to constantly keep up with.

I haven't done much LiveView/Phoenix with it, but my biggest "wish this were otherwise" is the chasm between Elixir and Erlang. There's a ton of basic stuff that when you ask, you get told to jump into this other domain with this other set of documentation. It's like being in Wales or something, and sometimes English works, and othertimes, you get told you have to switch to Welsh to enjoy the local experience (not meant as a diss to those that are trying to retain various indigenous languages against English throughout the Isles).


> Grouped aliases/imports/requires are a mis-feature in my view. They save a few characters when typing but they complicate searching for module uses and refactoring.

Totally agree with this. I prefer to not use aliases most of the time, or to explicitly list out each module path separately if they are too long to fit inline. All preferences though.

> Similarly, I don’t like comprehensions as everything they do can be achieved with functions, and they are not composable.

I don't use comprehensions too often, but I would miss them if they vanished. Some things are just plain easier with them. They are also valuable in unit tests to show better error messages[1]:

# Don't do... assert Enum.all?(posts, fn post -> %Post{} == post end)

# Do... for post <- posts, do: assert %Post{} == post

EDIT: Although I suppose you could just use Enum.each as well.

[1] https://keathley.io/blog/good-and-bad-elixir.html


Initially I didn't like grouping but lately I started to group aliases by their content/function. Like schemas together etc.

But that's really nitpicking.

Remembering the difference between require, use and import, now that is a struggle.


I've been coding elixir for 7 years and still don't quite understand why I have to keep typing "require Logger" to just use the logger, and why it isn't just required in the framework across the board.

Something to do with require being a macro and being lexically scoped (but you could probably just require it in the usings for Phoenix right?)


>> Grouped aliases/imports/requires are a mis-feature in my view. They save a few characters when typing but they complicate searching for module uses and refactoring.

> Totally agree with this. I prefer to not use aliases most of the time, or to explicitly list out each module path separately if they are too long to fit inline. All preferences though.

recode[0] has a formatter plugin that reformats grouped alias/import/requires

[0] https://github.com/hrzndhrn/recode


"if...I want a function which gets me the stats on active accounts ... should that function live in the Users, Companies or Accounts context?"

Answer: It should live in a new context. I would call it `Stats`.


> Live components are best avoided if possible, in my view. The documentation is quite bullish on them, presenting them as just another thing alongside functional components, but I think they should be more of a last resort, with functional components used whenever possible.

That's an interesting view. I tend to use Live Components whenever I'm rendering a list, because if you don't, you'll quickly notice how the entire list gets re-rendered and sent over the wire whenever one of the elements on the list changes.

Live Components act as a sort of a barrier for this, they have a long-lived persistent ID that uniquely identifies the component, so when you're rendering a list of them, only the modified component gets sent over the wire. The effect this has on bandwidth usage can be dramatic, depending on the number of items in your list.


> That's an interesting view. I tend to use Live Components whenever I'm rendering a list, because if you don't, you'll quickly notice how the entire list gets re-rendered and sent over the wire whenever one of the elements on the list changes.

That's what streams are for https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#s...


That's new! I'll go have a look, thanks.


I thought this was a great article! I've used Elixir professionally for several years and it all rings true to me.

The bit about Keyword lists I think doesn't mention the biggest aspect of it as I think about it: erlang compatibility. I don't think they make a ton of sense in a vacuum, but a list of 2-tuples where the first element is an atom has been the convention for function arguments in Erlang for a long time, as I understand it. And so Elixir kind of adopted it as well, since it inter-operates with Erlang so much and so well. I think a good "map" implementation wasn't even available in the early days of Erlang?

So, basically I just kind of "go with the flow" and use keyword lists for the final `opts \\ []` parameter of a function, but basically nowhere else.

I also agree with `for` comprehensions. I use it when meta-programming function definitions, but hardly anywhere else.

Also, I forgot that `unless` even exists! I don't think I've seen it in any code since my ruby days 7 years ago.


>Also, I forgot that `unless` even exists! I don't think I've seen it in any code since my ruby days 7 years ago.

Pascal has both a while loop and a repeat ... until loop (and a for loop too, with to and downto variants, IIRC.

And cannot remember for sure, but I think Unix shell has an unless statement, although you can use if with not too.


I agree on the origin of keyword lists but they do have a useufl property - the same key can appear multiple times. That's why they're right for varargs - because nothing stops the user using the same argument multiple times and sometimes (rarely) that makes for a good API.


The inconsistency of elixir is what turned me away from it. The author points that out as something to live with, that’s fine.

But it’s annoying for me. It reminds me of my time dealing with ruby and rails, and I absolutely hated the billions of ways to do one thing with ”cute” helpers.

These days I just pick Go or TS for backend. Go for most things but especially anything performance critical or real time, and TS for anything else that just needs to get done really fast and is just IO bound.

Go has only most just one or max 2-3 ways to do something.

TS takes the best parts of js and makes them shine brighter.

I also don’t see the value of live view. Is it really that superior to just putting together an API or ws connection, and having web/mobile clients consume it? It just sounds like over engineering to be cool to send actual view code instead of a bit of json that updates the UI in place.

Would love to discuss further and see if I’m wrong.


Why do so many feel so strongly against state?

In my 10+ year programming career not once have I ever wished something was functional. State simply is, dealing with it is a foundational concept in writing code.

"It's harder to test" -- so mock it in the tests.

"It's harder to reason about" -- you can see the references in your code and you can punch it into the debugger. If the state is implicit it is mentioned in the documentation that you've read right??

"It's hard to tell where changes come from" -- again, stepping through with a debugger makes this somewhat trivial. Find the line that turns state bad, then drill into whatever that line calls, repeat.

I'm not saying we should be adding external state willy-nilly. But it is hardly evil -- the dumb hacks that have appeared when a class or instance var would have done the trick boggle the mind.

Can someone please prove to me how FP isn't just another cargo cult?


You may be misunderstanding the relationship between FP and state.

We are not against state, quite the opposite, FP likes state so much that we put it on a pedestal! We make it explicit, often with functionality that makes it easy to spot, understand, and manipulate.

If you think about your questions, here is how I would answer it:

"It's harder to test" -- so let's make them simple to test without mocking them out (which would effectively remove them from the test).

"It's harder to reason about" -- so let's make it easy to reason about by making it explicit.

"It's hard to tell where changes come from" -- so let's add constructs for manipulating it.

It is not about antagonizing it, it is about making sure it is clear and intentional.

---

PS: the "evil bit" is more often associated to global mutable state, but don't let any FP language fool you, they all have global mutable state with explicit tools for manipulating them. In Elixir, you can even use tools such as LiveDashboard [1] to navigate all global mutable state in your application, making it very easy to introspect.

[1]: https://github.com/phoenixframework/phoenix_live_dashboard/


Erlang and Elixir are what could be called pragmatic FP.

The idea is that your base abstraction should be functional, you can then have a genserver for that abstraction which is stateful.

So let's take lists for example. Building up a list is functional. But I could have a ListServer which takes an item and prepends it to a list. And just holds on to that. So lots of processes can send it messages to add things in, and order in the list is determined by order the messages arrived.

The way the server manages state is

State -> function -> State'

And those functions are easy to test.

It makes for a nice experience building software, the default is functional and you drop in processes which hold state when you need them. Compared to classes where the default is state.


> In my 10+ year programming career not once have I ever wished something was functional.

I.. have trouble with this statement. I bet you have, but you thought about it differently. A function that COULD be a pure function but that takes some data structure then mutates it and returns nothing, well that's just bad programming.

Now, wanting the entire program to be functional, that's a different matter.


> well that's just bad programming.

There are a lot of situations where it is/may seem prudent to pass a reference to something, mutate it, then return


Only reason I can think of is performance.


There are. Yes. But the previous poster seems to imply they have NEVER seen code written like that that would be improved by making it a pure function that returns a value for the caller to assign. Never? That seems unlikely to me. Unless they've only worked with really really amazing programmers. Even then it seems unlikely.


You can do that in Erlang/Elixir too, though you are passing a name/ID of the thing, not a reference to the thing itself (not in the sense of C/C++ or Rust references anyway).


You can "just" do any of those things but code that is understandable without a debugger is strictly better than code that requires a debugger to grok.

> "It's harder to test" -- so mock it in the tests.

This however doesn't make sense, you can't "mock" state - the state and its side effects is what you're testing.


I guess it depends on the specific language but in my experience one can mock nearly anything the code references


What do you mean by state ?

Nobody is against "state", like, for sure, you need your data anyway.

What I see that people are calling "state" is just deriving your final state from your original data, like in FP but saving your derived data into memory with a different lifetime than your og data.

FP style don’t promote anything complicated other than just don’t save your derived data, just recalculate it each time. Doesn’t mean that you can’t have performance : in fact most of the time, the memory is the bottleneck anyway.

Storing your state makes you responsible for synchronization which is where your bugs are going to hide.


> What do you mean by state ?

External state. Class vars, instance vars, even (gasp) globals. OP goes on some slight tangents/comments regarding FP and state management which inspired my comment.


Probably because it is very easy to mess up when using state incorrectly. It has been years since the last time my program run without bugs for the first time. If you're a genius, then sure, whatever tool you may be using, you'll do fine. But I noticed that many mainstream programming languages introduce concepts that are easy to misuse, even the same global variables. I recently wrote a small python script in 40-50, having concise and separate functions, etc. And I still was stuck on a bug that turned out be a consequence of shared state. The real issue was far from the location I was observing the bug. I think those kind of bugs is quite common.

What it boils down to in the end, is your priorities. If you want a program that works 90% of the time and you don't care about the last 10%, then sure, stating your way through is quite natural. If you're sick of the small bugs that is the consequence of the language semantics and not the logic or design, then you'll start nailing down the factors that contribute to the erroneous nature, with state being just one part of it.

Based on my experience in python and trying to teach other python developers some fp (spoiler, it didn't work)


> Class vars, instance vars, even (gasp) globals.

No need to focus on language-specific implementation details. A map that you are passing around in Elixir will serve you just fine 95% of the time. For the other 5% you can store state in an Agent, GenServer or in the ETS node-wide cache (here are your global variables).

Not sure why you call FP a cargo cult btw, but in case you are interested in feedback: it makes you sound like arguing in bad faith.


State is unavoidable. But FP, rather than being just another cargo cult is simply a different approach to programming. In a nutshell: imperative languages arrive at a castle through piling stones on top of each other until they have the shape of the castle, and functional languages transform a pile of stones into a castle. The difference is subtle: the one is about how you do the work, the other is about the end result. There are several major consequences from using the one or the other technique and both have their advantages and disadvantages. FP is probably easier for people with a math background and imperative programming is probably easier for people that look at programming in the same way that you'd look at building something out of real world components.

State, that big bad thing is something that is unavoidable in programming because at some level you will have state and side effects. But the longer you can put off having state (preferably all the way to the top) the cleaner your code will be and the easier it will be to test. That is because state effectively complicates the number of dimensions that your test has to verify against. If there are 5 variables of state and 5 function parameters that may still be doable. But if your execution context contains 500 variables and your function takes 5 parameters you now have to test against all of the permutations of 505 elements to ensure your function does what it should do. And if your function is going to modify that state (rather than return a result) it gets even worse for every function that calls this one.

So functional programming helps in the same way that processes can encapsulate a certain amount of functionality within a limited scope: the scope of a function that has no side effects (so it does not modify the state of the program) and that does not look at anything other than its parameters is very well defined, if it works once with a certain set of parameters it will always do so with those parameters. So now you can write some pretty clean and simple test cases to verify this function once and for all and if it passes the tests you are able to make some guarantees about it.

This can turn your worldview of programming upside down, it is a very powerful technique and to suggest it is 'cargo cult' is potentially throwing out a very powerful tool.

hth


I view it as pendulum. I cut my teeth during the sunrise of OO doing Smalltalk, dining in the halls of Smalltalk and CLOS. And then like a religious explosion, everyone went "all in" for random and arbitrary definitions of "all in" to OO and we ended up with a huge mess. Now, everyone hates on OO. It's sad to me.

While OO has been on the outs for the last 10ish years, I feel like we've been awash in the other side of the coin. Instead of "all objects all the way down" we get all "all functions (and monads)" all the way down.

There seems to be two "middle of the road" paths out of this. You either get "multi paradigm" approaches where you get incomplete and difficult to reconcile partial implementations of a smorgasboard of the author's preferences and formative experiences writ large. The other is the less popular but kind of "let's just be pragmatic" approaches like Elixir/Erlang. Personally, I'm finding the latter more appealing. Multi paradigm languages create a "too many knobs" problem when solving problems. The simpler, pragmatic, but sometimes kind of kludgy/goofy languages allow me to just solve problems (even if I have to jump through a couple of hoops some times), instead of wondering which hoops are the best hoops to jump through.


Why would I want to mock when I could not mock?

Why would I want to jump into a debugger when it could be explicit in code?

Why would I want to jump over to documentation (which, if necessary, I did read btw, but thanks for the nag) that might be unnecessary. As others have pointed out, state is not bad, but implicit is (not to say we are never implicit about some things in Elixir).

All of this stuff is achievable in OO languages, just not always idiomatic.

Elixir is kind of an OO language. Processes are kind of like object instances with a bunch of added features that mainly make concurrency simple. It also forces you to be more mindful of when you want that kind of state (though processes don't have to have state).

It's also hard to describe but immutability feels really good. Sure, you can say that it's no big deal to think about implicit references and and generally not get tripped up by them, and you'd largely be right, but not having to think about them at all is a nice little weight off the mind.


Nobody is against state.

But mutable state, that's another thing as it's harder to test, to reason about and leads to more bugs. It's not evil, it just has some drawbacks.

Code that you have to use a debugger to reason about just isn't as good as code you can reason about without one.


The point is that it's desirable to reason about your state just from reading the code and not by using a debugger. And when state is changed and passed around explicitly, it's easier to do that.


Brooo look there is no state! Except that there is this massive state object being passed around!

I have experience from one Elm project at work and it is precisely like that, some massive state object that is passed around. No one wants to work with that project.


I tend to ignore the whole context thing in Phoenix, it confused me in the beginning when I was learning about Elixir and Phoenix. It's a design pattern instead of a core functionality of the framework.

Just think about what are good interfaces and abstractions. Seperate your core functionality from your "view" functionality as Phoenix sets it up for you anyway (App = core and AppWeb is your external view layer, being a webpage, json api, liveview, etc). You might end up with something that looks like a context and that's ok, but it shouldn't be what you start from.


I mainly think of contexts as what the "business layer" used to be in the Java world.

There's the code that maps to your databases (Ecto structs, like "User" that maps to a users table). Then there's the code that explains higher _business_ concepts, (like "register user" - which might insert a user, send an email, etc).

In ruby, for example, a lot of that stuff was just combined in the same active_record classes. So you could say user.register.send_email.

But now you'd do Users.register(user) and Users.send_email().

Just a different name for a pretty standard concept of separating your business level code from your objects as stored in the database.


Great article, thanks for sharing. I’ve been using elixir for the past 5 years, and I’ve had pretty much the same questions along the way…some of them I was able to figure out by myself, and others I only learn by reading comments of posts like yours. Thanks a lot!


Elixir gets way too much coverage on HN considering that so few people use it.

Unfortunately Elixir doesn't solve any problem which isn't already solved by more popular languages such as Golang, JavaScript/Node.js, Java, C# or Python and the result is neither more efficient nor more maintainable... But its lack of adoption is a problem as it limits your ability to access many powerful libraries and tools that are available when using other more popular languages.

Programming languages aren't so different from spoken languages... Linguists can nit pick various pros and cons of different languages but ultimately it's not an author's language which determines whether or not their novel will earn them a Pulitzer prize in literature... Yet there is a good reason why no serious author will write a novel in Klingon (and it has nothing to do with the characteristics of the language itself). Same goes for programming languages and programmers.


You can not directly compare a functional language with a bunch of imperative languages and if you really believe that Elixir doesn't solve any problems compared to those others then that is a strong indication you haven't spent enough time with all of those just yet. Please refrain from telling us what HN spends too much coverage on, the reason Elixir gets a lot of coverage is that the more experienced you are as a programmer the more you will come to appreciate the Erlang/BEAM/Elixir eco system for the strengths that it brings to the table.

In my opinion - which you may well disagree with - it is one of the most mature programming environments available with the caveat that it is less suitable for very high performance number crunching (but there are workarounds for that), and that by the time that you have the equivalent of the various Erlang/BEAM infrastructure components up and running in any other environment you probably would have time left over if you had done the same thing in Erlang/BEAM and it will be more solid besides.

Dumb remarks about Klingon are not going to make you any friends here, keep in mind that for a new person to a language any programming language looks like Klingon and it simply takes time and exposure to familiarize yourself.


All I'm saying is that language popularity and size of the ecosystem is very important... So important, in fact, that whatever marginal advantages Elixir proponents may perceive are irrelevant. The language just doesn't define the writer nor the coder. A good coder using a so-called 'horrible' language will produce much more maintainable code than an average coder using an 'amazing' language. OK, some languages are more geared towards certain problems, but how much better does Elixir have to be for a particular problem to warrant all the downsides (especially in terms of network effects)?


Does not sound like you even read the intro of the language, otherwise you would know its selling points (#1 is having predictable latency even on very bursty workloads).

Also, a gentle reminder that you don't determine what gets posted on HN. No point getting ticked off by it, maybe you should disengage from HN every now and then, it helps mental health.


> nor more maintainable

My humble opinion is that it is more maintainable. Additionally: more fun to use, has less warts than the other languages mentioned and provides really powerful tools out of the box in an easy-to-use manner. I'd be interested in a study observing work stress levels of employees using various languages - my assumption is that Elixir users have less stress.


Unless you are arguing that HN is arbitrarily prioritising Elixir related content then it’s getting exactly the amount of attention it deserves.


I love how all of the example langs you used make me want to claw my eyes out (except for maybe Go which I don’t know much about).


How much hands-on experience do you have with Elixir and Phoenix?

At the end of the day, every programming language can be used to solve problems, but you need hands-on experience to be able to judge the ease of writing, reading and maintaining a codebase in that ecosystem.


I fully agree. And since you are talking about popularity of languages in Web backend dev, don’t forget about PHP, which is still the silent beast in this area.


Yeah, everyone here is creating code in Elixir/Lisp/Haskell -- languagesI almost never see on any job board or job description

again, its fine to use them, just they get far too much attention for the amount they are used in the wider world


For reference, Django has been downloaded roughly 2.8 million times in the past week while Phoenix has been downloaded 240k times. There's no doubt that the user base of Elixir is smaller, but it's not as tiny as you seem to think.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: