24 ms·
Unpacking Elixir: Concurrency
- passion__desire 3y agoI liked this approachable talk I watched few years ago. I wish developing in Elixir was as easy as python. https://youtu.be/xoNRtWl4fZU https://youtu.be/xoNRtWl4fZU
- Dowwie 3y agoI've found Elixir to be easy to understand given what it's doing, and it's doing a lot more work than Python does for each application. The thing is, though, that there are a lot of declarative frameworks in Python that make applications go further without burdening a developer with low-level detail. Elixir does the same thing. Elixir and OTP abstract away a lot of complexity, at times too much. Import enough frameworks into your Python application and you'll be making a lot more decisions. Thinking about process management for every application you write (BEAM process, not OS process) pushes you up front to think about the paradigm that is used everywhere, and once you grasp what you're considering you can apply your understanding of concurrency when using other languages. This often happens when an Erlang/Elixir developer works with Rust: many programmers adopt message-passing rather than sharing state, and as they make their system more robust think about how to control threads/tasks, adopting OTP-like paradigms.
- lawik 3y agoWhat do you find more difficult? The achieving tasks in it or the getting paid to do so part?
- jasonjmcghee 3y agoThere’s definitely a bit of a learning curve if you’ve never written in a language where everything is immutable. Certain problems have to be approached in a fundamentally different way if you can’t mutate state. Fortunately there’s Agent/OTP, but again, pretty different than other (or popular with beginners - js/python/java) languages.
- throwawaymaths 3y agoYou really shouldn't be using agent for mutable state. Kind of annoying that the docs push you to that. Agent is most useful when you need to tie the transient lifetime of some state with something else (think: compilers). Otherwise, it's generally better to use a database, (or ets, if you need performance), GenServer/gen_statem if you need reactive events attached to the state. Agent is just a wrapper over GenServer so it is strictly worse in terms of performance, and honestly, slightly hard to grok as to where actions take place (and thus, who is responsible for failures)
- giraffe_lady 3y agoI love elixir and am very comfortable with functional programming and generally prefer it. But being told you have to write a state machine instead of just += a var is an excellent experience in believing elixir is harder than python.
- sodapopcan 3y agoOf course you example is hyperbole and I certainly understand what you are saying. But the attitude that many programmers take when learning a new language that something is "harder" when it's just different than what they are used to bothers me (to be clear, I'm not directing that at you). But comparing: i = 0 for x in [1, 2, 3, 4]: i += x vs i = Enum.reduce([1, 2, 3, 4], fn x, i -> i + x end) There is nothing inherently "harder" about the functional version, it's just different (and of course comes with its own benefits).
- jcparkyn 3y agoI find that pure functional loops with reduce or fold often get a lot harder to read/write as soon as you have more than one variable to keep track of. Imperative loops don't really have this problem.
- throwawaymaths 3y ago
- passion__desire 3y agoIf faced with the one-off problem shown in the video, I would have reached for a python solution like joblib etc. But knowing efficient better way to do would be additional tool. As shown in the video, it took him jumping through multiple hoops and expert help to get to his solution and without knowing beforehand if all the required pieces can be put together in Elixir ecosystem. No such hurdle in python solution.
- thibaut_barrere 3y agoComing from (a lot of) Ruby which is quite close from Python IMO, initially I felt Elixir was harder, but it was just because of my habits, and the huge "comfort vendor lock-in" that both Ruby & Python provide. Ultimately I am still as fluent with Ruby as before, but I find developing with Elixir is easier than Ruby (it took a few years to get there though).
- conradfr 3y agoI think a few code example would have been nice. The thing I like about concurrency in Elixir is that it's there if you need it but it's mostly not mandatory in your code, compared to Javascript where its kind of imposed on you even when it's an hindrance. I remember one trick I used to do with LiveView on click events etc was to put all async (or "asyncable") code in a spawn function, which would speed up the return.
- freedomben 3y agoYes exactly! This is super important for people new to elixir. The vast majority of the time, you don't even need to know that there is even any concurrency. You can write entire sophisticated Phoenix apps and never need it. But when you do eventually need it, it's there and it's wonderful, once you grok the pattern.
- bongobingo1 3y agoYes, I know this is mostly a +1 comment, but this is a huge differentiator from other systems I have worked in where "oh I need concurrency..." becomes a question of what libs, styles, where are the gremlins in the library, where are the state gremlins in our code, etc etc. Vs with Elixir you just sort of go "actually I need two of these. actually I need 200 of these. actually..." and it "just works". Not that there isn't any stuff to learn there, you have to understand how actors pass messages, how you can unintentionally bottle neck via call (caller waits for a reply) vs cast (caller keeps going), etc but its very surprise-free because, surprise surprise, Erlang has been built from the ground up for concurrency and generally worked out how it should work 30 years ago instead of bolting some keywords in.
- spinningslate 3y agoAgree. That's a big advantage of thread-based concurrency [0] vs async/await. The decision of whether to do things sequentially or concurrently sits with the caller, not the callee. Which means it's there where you need it and out of the way when you don't. With async/await, the decision is made by a function implementer somewhere down the stack, quite probably in 3rd party code. As the caller you get no choice because coloured functions [1]. You have to deal with concurrency whether your app needs it or not. -- [0] By thread I mean the general concept, not specifically OS threads. In this case it includes the fine-grained processes offered by the BEAM VM that underpins Elixir and Erlang. [1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
- out_of_protocol 3y agoUnlike javascript, elixir makes you write synchronous code, e.g. def process_items(items) do items |> Task.async_stream(&Processor.process/1, max_concurrency: 2, timeout: 7000, on_timeout: :kill_task) |> Enum.to_list() end Semantics of regular and concurrent code is the same
- Dowwie 3y agocomment here is conveying that there is no function color, where a language distinguishes standard synchronous calls from asynchronous calls
- strzibny 3y agoThis. I don't want to be in an event loop by default.
- throwawaymaths 3y agoHate to break it to you but in the BEAM you're always in an event loop.
- ch4s3 3y agoNo really. The scheduler isn’t really like an event loop. The scheduler will only allow a task so many units of execution before pulling it off the processing queue and scheduling other work. You can block an event loop, but not the scheduler.
- toast0 3y agoAdding preemption doesn't make it not an event loop, IMHO.
- ch4s3 3y agoThe context of the discussion was the node event loop, which doesn’t have preemption. The BEAM scheduler is only like an event loop when you’re locked to one physical processor core, otherwise the schedule is spreading work across multiple OS level threads. You could squint and call this an event loop manager with preemption, but I think the phrase “event loop” detracts from the clarity of any correct description.
- deleted 3y ago[deleted]
- deleted 3y ago[deleted]
- cutler 3y agoThe deal-breaker for me with Elixir has always been the lack of real Elixir vectors rather than the crappy Erlang array library. Yes, I know you can use a Map with numeric keys but that's not the same.
- brabel 3y agoWhat would be the difference between a "real vector" and a Map with numeric keys?
- peoplefromibiza 3y agoMaps are hashtables, Vectors are a contiguous area of memory where each element can be access by index referring to a specific address in that memory. Vectors are usually also homogeneous about the data they hold, because each element should occupy the same fixed amount of memory, such as i*item_size gievs you back the offset of the element i in memory. Anyway, in Elixir you can use the Erlang's :array module
- throwawaymaths 3y agoI'm pretty sure erlangs :array is just a skin over tuple, so it's ~O(1) but not contiguous. might be O(log n) for dynamic arrays. The only truly array datatype in Erlang is :atomics
- davydog187 3y agoErlang tuples are contiguous in memory for primitive values https://blog.edfine.io/blog/2016/06/28/erlang-data-representation/ https://blog.edfine.io/blog/2016/06/28/erlang-data-represent...
- throwawaymaths 3y agoArray doesn't guarantee to be put into a single tuple. It might be nested.
- jug 3y ago> If I do want to do concurrent work there is the usual async/await mechanisms, implemented in the Task module. Not as weird as in JS. They just return a Task reference which is a helpful abstraction on top of the Process ID or PID. Then you can await on it to get a result or await on multiple to get multiple results. There is also a wicked function called Task.async_stream which will take an enumerable (list, map, stream or similar) and run a Task for each entry as a lazy stream. By default it has a concurrency matching the number of available cores (just like the schedulers). This is essentially a fancy shortcut for using all your machine has to offer in the service of getting the work done for tasks that are embarrassingly concurrent. It is very fun to use when bodging together scripts that you want to go fast. Hmm, am I reading about Elixir or C#... ;-) Yes, there are other major differences but this entire paragraph can be taken verbatim for C#.
- lawik 3y agoYeah, the API is very similar. I don't know the .Net runtime well enough to say anything about how it actually executes though.
- deleted 3y ago[deleted]
- h0l0cube 3y agoOne of those major differences would be that each ‘process’ is incredibly lightweight, and a memory model that allows you to spawn millions of them without having to think about memory allocation or cpu contention
- samsquire 3y agoThanks for the article. I haven't written much Erlang. I am looking for a better representation of concurrency and in my thinking I've found that it feels easier to understand is a timeline grid with rows for independent processeses and columns for time with demarcations for events. You could say a sequence diagram is closest but I'm also thinking of Chrome developer tools with its renderings for rendering, painting, javascript etc. http://bloom-lang.net/ http://bloom-lang.net/ solves the nonorderly concurrency problems between machines with lattices. I am unsatisfied how concurrency is represented in the mainstream languages. I find Rust async hard to understand and read at a high level from a schedule perspective. The function colouring issue is painful. I want an interactive programming environment that lets me create a "tcp-connection" for instance and then register handlers for "on-ready-for-reading" and "on-ready-for-writing" which are system events that are triggered from IO threads that run epoll or liburing. You don't want to block the IO thread loop so you dispatch an event to another thread. I've been designing a syntax for representing state machines and events that can be fired from different places, it looks like this: initialstate = state1 | state1a state1b state1c | state2a state2b state2d | state3 It waits for initialstate to be reached, then waits for state1 then it waits for state1a state1b state1c in any order and so on. Ideally we want data flow to be a tree and sprinkled with synchronization points which are barriers where independent tasks synchronizes, where we exchange tasks and data. Shared memory synchronization is great for the amount of data that can be transferred in one-go (you're not writing lots of data into a pipe as in multiprocessing or into a buffer in message passing, message passing can be O(1)) but I don't want to do it on the hot path, Amdahls law. Another program I've written with Java runtime executes the following program: thread(s) = state1(yes) | send(message) | receive(message2); thread(r) = state1(yes) | receive(message) | send(message2); I've been trying to design a multithreaded server architecture here https://github.com/samsquire/three-tier-multithreaded-architecture https://github.com/samsquire/three-tier-multithreaded-archit... You could implement complicated interlocking workflows with this syntax, because you just need to wait for events as defined. LMAX Disruptor gets some good requests per second
- di4na 3y agoCan i recommend you to take a look at effect handlers languages? It is not mainstream but it may prove useful for your thinking here. Effekt or Ocaml 5.0 new events handler system may help get ideas that help you achieve your goal here.
- launchtoast 3y agoTotally agree with conradfr
- tombert 3y agoDisckaimer: I have never used Elixir in any serious capacity, but I have done a good chunk of Erlang. Concurrency in Erlang sort of frustrates me...not because it's bad, but because when I use it I start getting pissed at how annoying concurrency is in nearly every other language. So much of distributed systems tooling in 2023 is basically just there to port over Erlang constructs to more mainstream languages. Obviously you can get a working distributed system by gluing together caches and queues and busses and RPC, but Erlang gives you all that out of the box, and it all works.
- brigadier132 3y agoCan you give some examples?
- fredrikholm 3y agoIf you want to dig into some code: The Elixir 'Getting started'[0] guide has you building a concurrent, distributed KV store using nothing but the basics of OTP (effectively the std of BEAM). 0. https://elixir-lang.org/getting-started/introduction.html https://elixir-lang.org/getting-started/introduction.html
- macintux 3y agoI fell in love with Erlang pretty quickly, and it’s hard for me to enjoy writing other languages. Simple concise syntax, pattern matching, immutability, error handling without branches all over the place, concurrency… it’s hard to walk away from that.
- ralphc 3y agoHave you tried Elixir? So many blogs etc. talk about coming to Elixir from Ruby, which I don't understand, little similarity beyond def...do...end. Elixir uses the same VM with a different, some including myself would say better syntax. Do you still prefer Erlang syntax?
- 3y ago
- deleted 3y ago[deleted]
- xwowsersx 3y agoElixir with static typing would be an absolute dream. I know there's https://gleam.run/ https://gleam.run/
- mikhailfranco 3y agoIt's not possible, because functions, pattern matching, guards, typespecs and success typing are fundamentally incommensurable: each specifies and/or enforces a subset of the other, they are all useful in their own way, but not isomorphic. Formal attempts have repeatedly failed, even when they exclude message passing, which is the most difficult (and most useful) aspect. If Wadler cannot do it, I'm sure I (or you) cannot. A typesafe concurrent language is possible, but only a new language layer on top of E/E and the BEAM.
- felideon 3y agoAre you saying Gleam has failed?
- mhanberg 3y agoAre you aware of any of the recent advances? There is the set theoretic types work [1] lead by Jose and a couple of PhDs and also eqwalizer by WhatsApp [2] [1] https://elixir-lang.org/blog/2022/10/05/my-future-with-elixir-set-theoretic-types/ https://elixir-lang.org/blog/2022/10/05/my-future-with-elixi... [2] https://github.com/WhatsApp/eqwalizer https://github.com/WhatsApp/eqwalizer
- lawn 3y agoIn addition to gleam, there's also ongoing attempts to add static typing to Elixir.
- nithinbekal 3y agoThere are plans to introduce type system into Elixir: https://elixir-lang.org/blog/2023/06/22/type-system-updates-research-dev/ https://elixir-lang.org/blog/2023/06/22/type-system-updates-...
- beeburrt 3y agoI like this Underjord guy. I've watched some of his YouTube content on Elixir. He's good at explaining concepts, has a natural teaching ability.
- lawik 3y agoMade my day
- spacedimp 3y agoSpeaking of Elixir, I wrote a simple guide on getting an Elixir web app set up in podman (safer Docker). Also it uses Plug to demonstrate how the framework Phoenix is built. https://spacedimp.com/blog/dockerless-setting-up-an-elixir-webapp-using-podman-and-plug/ https://spacedimp.com/blog/dockerless-setting-up-an-elixir-w...