12 ms·
While I agree, the article doesn't really give concrete examples. Elixir is layered, making it easy to learn and master. You can get pretty far with Phoenix wi
by latch 6y ago
While I agree, the article doesn't really give concrete examples.
Elixir is layered, making it easy to learn and master. You can get pretty far with Phoenix without ever understanding (or even knowing about) the more fundamental building blocks or the runtime. In large part, this is because of its ruby-inspired syntax. You'll have to adjust to immutability, but that's pretty much it.
Then one day you'll want to share state between requests and you'll realize that the immutability (which you're already comfortable with at this point) goes beyond just local variables: it's strictly enforced by these things called "processes". And you'll copy and paste a higher-level construct like an Agent or Genserver and add the 1 line of code to this root supervisor that was just a file auto-generated in your project. But that'll get you a) introduced to the actor model and b) thinking about messaging while c) not ever worrying about or messing up concurrency.
Then you'll want to do something with TCP or UDP and you'll see these same patterns cohesively expressed between the runtime, the standard library and the language.
Then you'll wan to do something distributed, and everything you've learnt about single-node development becomes applicable to distributed systems.
Maybe the only part of Elixir which can get complicated are Macros / metaprogramming. But you can get far without ever understanding this, and Phoenix is so full of magic (which isn't a good thing), that by the time you do need it, you'll certainly have peaked behind the covers once or twice.
The synergy between the runtime, standard library and language, backed by the actor model + immutability is a huge productivity win. It's significantly different (to a point where I think it's way more accurate to group Ruby with Go than with Elixir), but, as I've tried to explain, very approachable.
- searchableguy 6y agoYeah, elixir provides Registry which is what I use for simple state management or directly use ets. See below (jswny) for more clarification.
- laser 6y agoUnfortunately had a bug I found today in which an event_notifier genserver was calling a method that checked against the database for the the state of an Event, to decide whether to send a notification and then update the event to record that notifications were sent and upon successful update send the notifications. But in the query to construct the list of users that should be notified, the User module was not imported at the top of the file and the notifications were failing to send. Of course, if the path had ever been run, it would have obviously failed and been the simplest type of error to rectify. This is why it's generally not an issue and is actually nice as it gives you direct visibility into dependencies. But, on rare exception, when you don't test the path because it's dependent on time-dependent state and are overly confident in the suitability of the untested code you're deploying to production, you get bit by the compiler that quietly warns and lack of static analysis (oh nice writing this comment just made me discover https://github.com/rrrene/credo https://github.com/rrrene/credo)
- deallocator 6y agoYou might also be interested in https://github.com/jeremyjh/dialyxir https://github.com/jeremyjh/dialyxir I use both dialyxir and Credo in my projects
- depr 6y agoYou can run the compiler with --warnings-as-errors
- jswny 6y agoCan you explain what you mean about module naming? I’ve done a good amount of Elixir, and I’m puzzled because I don’t know what you are referring to at all. Most libraries and frameworks I’ve used enforce things on your modules via behaviors which have nothing to do with naming. Perhaps you are talking about naming functions for behaviors? Because I think it’s the same in most languages that “overridden” or “implementations” of functions for modules/classes that use some kind of interface must be named the same as the interface definition.
- searchableguy 6y ago> Perhaps you are talking about naming functions for behaviors? Because I think it’s the same in most languages that “overridden” or “implementations” of functions for modules/classes that use some kind of interface must be named the same as the interface definition. Yes. In typescript, you can have abstract classes or interfaces to implement which gives you more information (intellisense, visible errors, type information) and make the contract more explicit. I was trying out phoenix and I didn't get any hints about the methods to implement in the module. I was relying on documentation for that. If there was a way to say use @some_spec and get information about it in the editor. It would have helped out.
- shadowfacts 6y agoThe compiler will warn you about things like unimplemented functions that are required by behaviors, and there are plugins for various editors to display errors/warnings inline. There's also Dialyzer (and various IDE integrations) for some level of static type-checking.
- JohnHaugeland 6y agothere are language features directly for this. no need to rely on compiler hints or dialyzer. elixir people really should learn erlang. if you ever saw a coffeescript person struggling to do things that are straightforward in js, because they didn't bother to learn js? that's what you're doing now
- JohnHaugeland 6y agoIf you learned Erlang, you'd realize these were both terrible choices, and that you ought to be in the process dictionary
- searchableguy 6y agoCan you explain more? I am looking at the answers from this stackoverflow question: https://stackoverflow.com/questions/1483550/in-erlang-what-are-the-benefits-of-using-ets-instead-of-process-dictionary-with/9819524 https://stackoverflow.com/questions/1483550/in-erlang-what-a... and the Erlang docs. I don't get the impression that they are comparable.
- emerongi 6y agoThey are not. There is a reason why seasoned Erlangers all tell you to avoid the process dictionary. Erlang docs[0] say the following: > Note that using the Process Dictionary: > - Destroys referencial transparency > - Makes debugging difficult > - Survives Catch/Throw > So: > - Use with care > - Do not over use - try the clean version first [0] https://www.erlang.org/course/advanced#dict https://www.erlang.org/course/advanced#dict
- JohnHaugeland 6y agoHi, I'm a seasoned erlanger, and we do not all tell you that. The things you quoted and didn't write yourself are not real problems here. Erlang in general doesn't actually have referential transparency, and it's not clear why this author thought that it did. It's not clear why the process dictionary would "destroy referential transparency." Erlang doesn't have references, and the PD is just a storage model. Surviving catch/throw is a benefit, not a problem I'm not sure why this author is asserting that it "makes debugging difficult." That's just bullshit. What makes debugging difficult is state, and if you need to track state, switching storage methods won't make that easier. "Try the clean version first" - what do you imagine is the clean version here? You're just repeating things you don't understand, cargo cult style.
- JohnHaugeland 6y agoThe worst part about Elixir is watching it teach all these programmers words incorrectly, and realizing how damaged they will be if they attempt to enter mainstream languages
- deallocator 6y agoCan you clarify what you mean by this?
- JohnHaugeland 6y agoErlang doesn't offer immutability. It offers a lack of name rebinding. If you want to see why this difference matters, go learn Elixir, and try creating a tight loop without any recursion during which all you do is reassign the value of a variable a billion times. Notice the ram usage of the machine go to the moon. Then there's this thing about immutability being guaranteed by processes. No it isn't, though? You just copy the data and operate on a copy (or a fragment, sometimes, in the case of binaries.) The original can be mutated, and if you've scaled anything in erlang, you've seen this cause a bunch of problems around the array module Erlang isn't actor model (sometimes you hear it called pi calculus, which it also isn't.) These are things that seem casually, superficially similar to what Erlang is, but then, the whole point of the actor model and the pi calculus is to give mathematical rigor to guarantees that Erlang doesn't offer, and then you've confused yourself into thinking that Erlang has properties it doesn't actually have. Engineering by failed metaphor is tremendously dangerous, because it makes people feel safe in ways they aren't, and just due to the nature of systems, the way you find out is often under terrible pressure and non-rarely unfixable. These "everybody knows" beliefs are really fucking bad. REALLY bad. You can see some examples here: https://aphyr.com/posts/288-the-network-is-reliable https://aphyr.com/posts/288-the-network-is-reliable . "not ever worrying about or messing up concurrency" Like. How new do you have to do be to Erlang and Elixir to think that message passing means you can't mess up concurrency? If you ask a C programmer to name some threading problems, five dollars says one of the first three they name is some variation on lock contention. Messaging does not solve this. (Arguably it makes it worse, since every blocking process is now effectively a lock, which is a generally discouraged design strategy in most languages but encouraged as a primitive in this language.) Indeed, just as OO problems got more common in C++ vs C because of how easy OO became to do, and how frequent it became, I actually end up seeing a lot more concurrency related problems in Erlang than in most languages, just because there's a lot more concurrency than in most languages. And this isn't be complaining about erlang; I love it. But also, if you've just upgraded from bows to rifles, you have to understand that with that improvement will also come an increase in accidental bullet injuries. Saying you'll never have concurrency problems in erlang is like saying you'll never have allocation problems in C++. It suggests that everything you know about the language is reading chapter three's discussion about how tools are made available to mitigate this (in C++'s example it'd probably be a SFINAE discussion or something naive about automatic deletion, or if it's an old enough document, maybe smart pointers.) That that tooling exists suggests there's an active problem being actively mitigated, not that the problem is solved in blub and not everywhere else. There's a reason you don't see cut outside prolog. In order to abuse one of my favorite community metaphors, "the reason footguns exist is that sometimes you actually do need to shoot lots of animal at once; don't take it out of the case unless Dracula shows up, but, in the balance, eventually you'll be glad you know [Burt Gummer](https://www.youtube.com/watch?v=SdwFp2D5Eg8 https://www.youtube.com/watch?v=SdwFp2D5Eg8)." . > Then you'll wan to do something distributed, and everything you've learnt about single-node development becomes applicable to distributed systems. This just makes me laugh, sadly. "Because I passed messages in a message passing language, I'm now ready to write distributed systems" is just the most delightfully naïve claim There are red flags. Red flags are a thing that exists. And the thing is, you see them all the time in places you shouldn't. [This is correct](https://martin.kleppmann.com/2015/05/11/please-stop-calling-databases-cp-or-ap.html https://martin.kleppmann.com/2015/05/11/please-stop-calling-...) and yet my trainers at two FAANG companies claimed that they had internally solved this at large scale within the company, on down from on high from the biggest people in the space You see fucking Oracle get that wrong Like the very first three things you ask about distributed systems are: 1. how well does it tolerate partitions, 2. how well does it perform over a WAN, and 3. did you design for both city loss and provider loss With erlang, the traditional answers are 1. no-ish (that is, only just barely and by throwing away everything you used to love about the language to get a hokey election system that barely works and seems designed for the explicit purpose of causing merge failures (it's the CVS of rejoins, i swear to your god) ) 2. (loud, plaintive sobbing) 3. we stood up three copies of the system, does that count? what do you mean transactional The people saying that Erlang solves distributed systems are the people who stood up an ejabberd instance on their desktop and one on their laptop and declared themselves the vice president of MS Azure Engineering like yes, it does get rid of a few problems, and it has some convneiences, but what would you do if someone told you haskell solved all logic errors? some offered responses include: 1. believe them, drop everything, and make all life a burrito 2. say "wow, this person isn't very good at haskell, are they?" . > The synergy between the runtime, standard library and language, backed by the actor model + immutability is a huge productivity win. It's significantly different (to a point where I think it's way more accurate to group Ruby with Go than with Elixir), but, as I've tried to explain, very approachable. I read this paragraph out loud and suddenly I had two people making toothpaste commercials working for me I don't understand what happened. Am I a marketer now?
- discordianfish 6y ago> Elixir is layered, making it easy to learn and master. You can get pretty far with Phoenix without ever understanding (or even knowing about) the more fundamental building blocks or the runtime. In large part, this is because of its ruby-inspired syntax. You'll have to adjust to immutability, but that's pretty much it. This is my main problem with elixir. Even for senior developers it's hard to know how the building blocks and runtime works and since all abstractions are leaky, you'll end up with mysterious issues that only make sense once you understand all the layers. And in case of elixir there are lots.
- dynamite-ready 6y agoElixir's syntax is very nice, but at it's core, the Elixir/Erlang/OTP Actor model thing is quite different to other forms of programming.
- emadb 6y agoActually Elixir and Erlang are at the same level: they both run on the BEAM. So you need to know the BEAM that it is like a virtual machine that act differently from what we are used to (JVM, .NET). Once you learn the BEAM everything become clear and easy to use. IMHO Process state isolation and message passing are the core, that's not so difficult to learn and apply.
- discordianfish 6y agoYes, you need to learn the BEAM VM which makes it not easy to learn. It still might be worth it, but now you need to understand Elixir, Erlang, BEAM - additionally to your OS's API. All I can offer are anecdotes but I've worked at one company with a sizeable rabbitmq cluster where we were fortunate enough to have a guy in a different team who was a BEAM maintainer in his free time. He was the only one able to debug issues (mostly around unexpected memory usage). At another place, we had some in house elixir services. Developers were very happy and productive with it, but we also ran into unexpected memory usage and crashes due to that and to the day their root cause are unclear, even after adding detailed telemetry to monitor process counts and memory profiles. I guess I have a general issue with VM based languages, but that might be due to the fact that I spend more time debugging issues than writing software and I appreciate the common libc/syscall api used in VM-less languages.
- thdrdt 6y ago"and Phoenix is so full of magic (which isn't a good thing)" Can you explain something more about why you think this is not a good thing? Is it like Laravel (PHP)? Lots of magic so it is very easy to get started but a hell to maintain? I really like to get started with Elixir (for webdev) but I am not sure if I should start with Phoenix.
- headmelted 6y ago“Magic” in any language is always bad. It makes for great demos but it basically means “this just works so you don’t need to know how it works”. The problem is you always need to know how your tools work, often sooner rather than later, as you inevitably end up with edge cases and bugs in your application that simply can’t be resolved without that understanding.
- technicolorwhat 6y agoWhere does one draw the line between magic and a lack of user understanding? I never got this argument. Isn't magic you as a user not understanding how it works? I mean, classes in c are pretty magic, the this pointer is magic. Type checking, loops, trampoline jumps. A car is pretty much magic yet people use it every day. Is it magic when it does something convenient for you based on conventions without you writing the steps? As long as 'magic' is clearly explained in the docs I don't see how it's different from a layer of abstraction like a compiler or a car.
- michaelcampbell 6y ago> Where does one draw the line between magic and a lack of user understanding? There is no line; that's the definition. (I'm agreeing with you here, btw) It's a blub issue; if one doesn't understand it, it's derisively called "magic". So you don't get this argument because it's totally based on the observer. I use Django at work and I hate it because it's magic all over the place. And I recognize that me saying it is simply I haven't had the time or impetus to go figure out the stuff I don't have figured out yet, so I can bucketize all that as "magic".
- nickjj 6y ago> copy and paste a higher-level construct like an Agent or Genserver and add the 1 line of code to this root supervisor that was just a file auto-generated in your project. But that'll get you a) introduced to the actor model and b) thinking about messaging while c) not ever worrying about or messing up concurrency. Isn't it well known that GenServers can become severe bottlenecks unless you know the inner workings of everything to the point where you're an expert? I'm not an Elixir expert or even used a GenServer in practice but I remember reading some warnings about using GenServers around performance because they can only handle 1 request at a time and it's super easy to bring down your whole system if you don't know what you're doing. This blog post explains how that happens: https://www.cogini.com/blog/avoiding-genserver-bottlenecks/ https://www.cogini.com/blog/avoiding-genserver-bottlenecks/ And I remember seeing a lot of forum posts around the dangers of using GenServers (unless you know what you're doing). It's not really as easy as just copy / pasting something, adding 1 line and you're done. You need to put in serious time and effort to understand the intricacies of a very complex system (BEAM, OTP) if you plan to leave the world of only caring about local function execution. And as that blog post mentions, it recommends using ETS but Google says ETS isn't distributed. So now suddenly you're stuck only being able to work with 1 machine. This is a bit more limiting than using Python or Ruby and deciding to share your state in Redis. This really does typically require adding a few lines of code and now your state is saved in an external service and now you're free to scale to as many web servers you want until Redis becomes a bottleneck (which it likely never will). You can also freely restart your web servers without losing what's in Redis. I know you can do distributed state in Elixir too, but it doesn't seem as easy as it is in other languages. And it's especially more complicated / less pragmatic than other tech stacks because almost every other tech stack all use the same tools to share external state so it's a super documented and well thought out problem.
- ericmj 6y ago> And I remember seeing a lot of forum posts around the dangers of using GenServers (unless you know what you're doing). The danger is using a single process of the GenServer instead of multiple, so you can get single process bottleneck that won’t use multiple cores. You don’t have to know any intricacies of BEAM or OTP to know and design around using multiple process instances of the GenServer. > I know you can do distributed state in Elixir too, but it doesn't seem as easy as it is in other languages. You can use Redis in Elixir as well. Saying that Elixir is worse at distribution than Python/Ruby because ETS isn’t distributed is a bit like saying Python is bad at distribution because objects are not distributed. It’s especially strange since Elixir ships with a distribution system (so you can access ETS from other machines) while your other example languages do not.
- bcrosby95 6y agoI'll take Elixir's macros and metaprogramming over Java's solution: annotations. With macros you can easily find and examine what it's doing. But annotations are just tags that do nothing, and god knows where the code that actually does anything as a result of them resides.
- hosh 6y agoTo be fair, Elixir also allows developers to tag functions, which can then trigger macros. Some of them (like @doc) does not transform code, but others (like @impl, or Appsignal’s transaction decorators) will.
- nobleach 6y agoI'm not disagreeing with that sentiment, but I learned a considerable amount while implementing my own annotations. I do agree that they can be maddening... especially in the case of Spring Boot where you can forget to add a @Component annotation, and unit tests will pass... then you'll deploy your app and immediately see it blow up in prod.
- princevegeta89 6y agoYup the annotationPorn on Java is so unfortunate. I'll take any alternative to it any day.
- rocqua 6y agoThis actually sounds really cool. How is it for performance as compared to - Hand written single-threaded C - Python code stringing numpy call together. (I know these are very different things). Whenever I dip into concurrency for performance I always feel like things could be so much better.
- lostcolony 6y agoProbably not appropriate for CPU bound loads like that. Erlang/Elixir would be fine as an orchestration language calling out to C code to actually do the number crunching, but you likely won't see much of the benefit of the language doing that unless you're doing something more complicated in that layer. If it's a super thin layer of glue, Python is better suited (not least because Numpy is familiar and meant to work with the language)
- stjohnswarts 6y agoHave you considered OpenMP for something like that? Also I'll go ahead and add that rust has nice parallelization attributes as well
- rocqua 6y agoYeah, I've worked a bit with OpenMP. I felt it didn't quite have the ergonomics of an actor model that I would want. I might be wrong there though. I don't have any experience with any other compute limited systems that use the actor model. Rust certainly seems quite interesting, but also quite daunting.