[HN Gopher] Concurrency in Haskell: Fast, Simple, Correct
___________________________________________________________________
Concurrency in Haskell: Fast, Simple, Correct
Author : ingve
Score : 181 points
Date : 2025-04-14 10:47 UTC (3 days ago)
(HTM) web link (bitbashing.io)
(TXT) w3m dump (bitbashing.io)
| haskell17373 wrote:
| It's maybe interesting to note that the `async` library in use
| here is very simple and easy to understand. Nearly every function
| is one or two lines. Likewise `TQueue` is extremely simple (and
| easy to prove correct) thanks to STM, and also generally has good
| performance.
| zozbot234 wrote:
| A lot of the complexity here is just hidden in Haskell's
| runtime, which implements async processing based on green
| threads, besides other features such as GC. Though to be fair,
| the software transactional memory (STM) featureset is quite
| unique to Haskell since it relies on the availability of pure
| functions to ensure correctness. It's kind of hard to imagine a
| full equivalent to it in other well-known languages.
| juliangamble wrote:
| Quibble: Both Clojure and Scala have a Software Transactional
| Memory implementation, and the original Clojure Ant demo
| showed this.
| mikojan wrote:
| In another life I will be a Haskell programmer
| Avicebron wrote:
| Yeah me too, I'll invest in bitcoin early, live like a hermit
| off a coast somewhere, and school kids on HN, "cabal hell! I'll
| scream, no, Conda hell with powershell hooks in vscode you
| ingrates, my llm"
| revskill wrote:
| Why not python ?
| linguae wrote:
| I'm not the OP, but static types, Hindley-Milner type
| inference, algebraic data types, and pattern matching can be
| quite ergonomic. I have also come to appreciate functional
| programming and how it makes reasoning about and testing code
| easier.
| kqr wrote:
| Yeah, just last week I updated the numeric precision of a
| value across an entire user journey in a big enterprise
| application, spanning many functions and data types. Thanks
| to Haskell, I could do it in a day.
|
| In any other language I've used (barring maybe Ada) that is
| a refactoring that would take at least days, if not weeks,
| to manually track down all the places it interacts with the
| system directly or indirectly, because mixing e.g. int with
| long long is not a type error.
|
| In Haskell, I change the type and the compiler spits out a
| list of locations that needs to change. This is repeated
| for a few iterations until all transitive interactions are
| worked out. Done!
| tasuki wrote:
| No one _wants to_ be a python programmer. It 's a practical
| language to get things done. It isn't a language to make you
| feel proud of yourself nor about the current state of our
| industry.
| Philpax wrote:
| The concept of "the Pythonic Way" indicates to me that
| there are people who are proud of being Python programmers.
|
| May God have mercy on their souls.
| sgarland wrote:
| I've enjoyed making it go faster by finding quirks, but at
| this point it's mostly become "OK, what else can I offload
| to C?"
|
| I should really learn Rust. Or Zig. I tried Nim (best of
| both worlds, Python-esque code that compiles to C!), but it
| wasn't nearly as fast as my Python + C for my specific use
| case.
| knighthack wrote:
| What exactly do you write, where your Python+C is faster
| than Nim which compiles to optimized C?
| sgarland wrote:
| Generating millions of rows of synthetic data for testing
| RDBMS.
|
| Tbf, I didn't spend much time trying to optimize the Nim
| code once I got a working PoC, so it's entirely possible
| that I could've made it faster.
| cb321 wrote:
| You may no longer be interested in this kind of thing,
| but if you are there might be some ideas of note over at
| https://github.com/c-blake/nio/blob/main/db-bench.md (in
| particular the demo/gbyGen.nim program).
| whateveracct wrote:
| It's a lot of fun!
| runeks wrote:
| I fear that in another life I will be a JavaScript programmer
| kreetx wrote:
| Choose this life instead!
| cosmic_quanta wrote:
| There's no time like the present. Feel free to reach out if I
| can help you along your journey
| tasuki wrote:
| Not the person you're replying to, but I'll bite:
|
| I've written low thousands of lines of Haskell. Similar to
| mikojan, I love Haskell in theory, but ended up not enjoying
| it as much in practice.
|
| 1. The multitude of string-y types. I end up converting
| between String, Text, Lazy Text, ByteString, Lazy ByteString,
| and I forget what else. Each library wants me to pass in a
| specific string type, and each other library returns a
| different string type. LLMs are good at this, also for a
| while I had a ton of helper functions to convert between any
| two string types. Still, perhaps there's a better way?
|
| 2. The error messages. I come from Elm, so I'm spoiled. Yes,
| I understand a language with HKTs will never have as nice
| error messages. Yes, LLMs are pretty good at explaining GHC
| error messages.
|
| 3. The stdlib. Haskell gets a lot of credit for safety, but a
| `head` blows up instead of returning a `Maybe`. I know there
| are other - safer - preludes, but I don't know how to choose
| between them. I don't know how using a different prelude
| would impact my projects.
|
| I feel like my next step is either towards Idris, with its
| polished standard library (and dependent types baked into the
| language!), or towards something simpler and more Elm-like
| (Gleam perhaps, or Roc). But if you can sell me on Haskell,
| I'm all ears!
| cosmic_quanta wrote:
| I'm not going to sell you on anything. All of the things
| you've mentioned are true. Loosely, the multitude of string
| types and the state of the standard library come from the
| same place: the language is 30+ years old! There are many
| warts to be found.
|
| However, if you decide to start learning, the path is hard,
| especially if you come from a non-computer-science
| background like me. I attempted to learn Haskell twice; I
| bounced off the first time, quite hard, and didn't try
| again for years.
|
| What worked for me is a combination of two things:
|
| * Having a goal in mind, that has nothing with the choice
| of language. For me, it was building a personal website
|
| * The book Haskell Programming from First Principles [0]
|
| and if you have more questions, reach out.
|
| [0]: https://haskellbook.com/
| ilrwbwrkhv wrote:
| Rust has a bunch of these while being maintainable.
| whateveracct wrote:
| What about Haskell concurrency isn't maintainable?
|
| The concurrency stuff in the stdlib + the mainstays in the
| ecosystem are pretty stable and noncontroversial..there's stuff
| in Haskell that churn but this is not one of them.
| mrkeen wrote:
| STM is Haskell's feature for safe shared&mutable state.
|
| What is Rust's feature for safe shared&mutable state?
| speed_spread wrote:
| That would be Arc<Mutex<T>>, which works but is no STM.
| Philpax wrote:
| Additionally, the actor model is pretty easy to implement:
| https://ryhl.io/blog/actors-with-tokio/
| zozbot234 wrote:
| Relevant: https://github.com/Marthog/rust-stm which has usage
| instructions. It's memory safe as defined in Safe Rust, but
| unlike the Haskell implementation it's not "safe" in a
| correctness sense, because Rust does not afford the same
| control about mutability and purity. (At least, not yet -
| future additions to the language may improve this somewhat.)
| ykonstant wrote:
| Isn't concurrency in Rust a notorious pain point? Or am I
| confusing it with async which is different? [I am stuck in an
| era before parallelism, so I don't really understand these
| things]
| mrkeen wrote:
| This why I haven't fully embraced Rust yet. Whenever I ask
| about safely mutating shared state (see sibling comment), I'm
| met with silence, or some comment like: Rust guarantees that
| you aren't mutating shared state.
| sriram_malhar wrote:
| I'm surprised you say this. The core type system guarantees
| is that there is no aliasing while mutating, and no
| mutation while it is aliased. You get single writer
| multiple reader for free without overhead.
|
| If you want multiple writers, you can always use the Arc
| container and use the built-in lock.
| mrkeen wrote:
| >> or some comment like: Rust guarantees that you aren't
| mutating shared state.
|
| > The core type system guarantees that there is no
| [sharing] while mutating, and no mutation while [sharing]
| speed_spread wrote:
| Correctness in concurrency is actually one of Rust's strong
| suit. Any pain felt is because Rust is low level and does not
| prescribe a single concurrency mechanism, leaving each coder
| to figure out the benefits and constraints of each library.
| FuckButtons wrote:
| I thought it was a bit odd that the author claims there's no
| mutexes in sight, the TVar is effectively a mutex guard unless
| I'm misunderstanding this? (I've written exactly 0 lines of
| Haskel). Or is the claim that the lack of ceremony and accidental
| complexity around threading is the real win for concurrency here?
| chongli wrote:
| No, a TVar is not a mutex guard. A TVar is a software
| transactional memory (STM) variable. STM works just like a
| database: you batch together a sequence of operations into a
| transaction and then execute them. During execution of a
| transaction, all changes made to the contents of the TVar are
| stored in a transaction log. If some other transaction occurs
| during the execution then the whole thing is aborted and re-
| run.
|
| This can take any ordinary Haskell data structure and give you
| a lock-free concurrent data structure with easy-to-use
| transactional semantics. How it performs is another matter!
| That depends on the amount of contention and the cost of re-
| playing transactions.
| whateveracct wrote:
| https://hackage.haskell.org/package/stm-containers
|
| This library is full of STM-oriented data structures. They
| perform better than a simple `TVar (Map k v)`.
|
| It's kind of a fun trick actually. The stock Map is just a
| tree. The STM Map is also a tree [1] but with TVars at each
| node. So this helps a lot with contention - you only contend
| along a "spine" instead of across the whole tree, which is
| O(log n).
|
| [1] Technically a HAMT a la unordered-containers - trie,
| tree, you get the idea :)
| quibono wrote:
| > How it performs is another matter!
|
| I know you say it depends on how much contention one sees but
| I'm interested in the performance hit. Also, is STM the
| "standard" (or accepted) way to do async in Haskell?
| dsign wrote:
| You are correct, Haskell has quite a few mutex-like types. MVar
| is one of them.
|
| However, if memory serves me right, TVar is a building block
| for the transactional memory subsystem. The guard on TVar with,
| say, modifyTVar is not really stopping execution at entrance
| but simply indicating that the block modifies the variable. In
| my mental model, some magic happens in an STM block that checks
| if two concurrent STM blocks acted upon the same data at the
| same time, and if so, it reverts the computations of one of the
| blocks and repeats them with new data.
|
| To my knowledge, Haskell is the only programming language
| (+runtime) that has a working transactional memory subsystem.
| It has been in the language for about 20 years, and in that
| time many have tried (and failed) to also implement STM.
| dionian wrote:
| https://zio.dev/reference/stm/
| mrkeen wrote:
| > Implication of Using STM Running I/O Inside STM-- There
| is a strict boundary between the STM world and the ZIO
| world. This boundary propagates even deeper because we are
| not allowed to execute arbitrary effects in the STM
| universe. Performing side effects and I/O operations inside
| a transaction is problematic. In the STM the only effect
| that exists is the STM itself. We cannot print something or
| launch a missile inside a transaction as it will
| nondeterministically get printed on every reties that
| transaction does that.
|
| Does Zio actually offer any protection here, or is it just
| telling the reader that they're on their own and should be
| wary of footguns?
| dionian wrote:
| I am not super familiar with it, great question - my
| guess would be its the latter! Does Haskell's provide
| protections?
| hackingonempty wrote:
| STM happens inside the STM monad while regular effects
| happen in the ZIO monad. If you try to do ZIO effects
| inside an STM transaction you'll get a type error.
|
| Scala doesn't enforce purity like Haskell though so it
| wont stop you if you call some normal Scala or Java code
| with side effects. In practice its not a problem because
| you're wrapping any effectful outside APIs before
| introducing them into your code.
| whateveracct wrote:
| I think Clojure has some kind of STM too?
|
| Haskell's STM is pretty world-class though. That's fair to
| say :)
| dwohnitmok wrote:
| Clojure's STM never really took off because, for various
| reasons, it's not as easy to compose as Haskell's (where
| you can build up a big library of STM blocks and piece them
| together at the very edges of your program). As such
| Clojure's STM implementation doesn't actually have a great
| reputation within the Clojure ecosystem where it isn't
| usually used in most production codebases (whereas in
| Haskell STM is often one of the first tools used in any
| production codebase with concurrency).
|
| Basically it's the difference between focusing only on
| transactional variables without having a good way of
| marking what is and isn't part of a larger transaction and
| having a higher-order abstraction of an `STM` action that
| clearly delineates what things are transactions and what
| aren't.
| pjmlp wrote:
| You missed a very important detail, the language runtime.
|
| While Haskell's runtime is designed for Haskell needs,
| Clojure has to be happy with whatever JVM designers
| considered relevant for Java the language, the same on
| the other platforms targeted by Clojure.
|
| This is yet another example of a platform being designed
| for a language, and being a guest language on a platform.
| dwohnitmok wrote:
| I don't think this is a limitation of the JVM. When I've
| used Clojure's STM implementation it's been perfectly
| serviceable (barring the composability issues I
| mentioned). Likewise when I've used the various STM
| libraries in Scala. Eta (basically a Haskell
| implementation on the JVM that unfortunately stalled in
| development) also had a fine STM implementation.
|
| It's more of a combination of API and language decisions
| rather than the underlying JVM.
| eduction wrote:
| My impression at least watching chatter over the last
| several years isn't that it has a bad reputation but
| rather that people haven't found a need for it, atoms are
| good enough for vast bulk of shared mutable state. Heck
| even Datomic, an actual bona fide database, doesn't need
| STM it's apparently all just an atom.
|
| But I've never heard someone say it messed up in any way,
| that it was buggy or hard to use or failed to deliver on
| its promises.
| robto wrote:
| Clojure atoms use STM, though. I've been writing Clojure
| for almost a decade now, it's not that STM isn't great,
| it's just that immutable data will carry you a very long
| way - you just don't need coordinated mutation except in
| very narrow circumstances. In those circumstances STM is
| great! I have no complaints. But it just doesn't come up
| very often.
| dwohnitmok wrote:
| I would say to the contrary it would come up all the time
| if the right idioms were in place.
|
| For example, when it comes to concurrent access to a map
| the Clojure community generally forces a dichotomy,
| either stick a standard Clojure map in an atom and get
| fully atomic semantics at the expense of serial write
| performance or use a Java ConcurrentMap at the expense of
| inter-key atomicity (or do a more gnarly atom around a
| map itself containing atoms which gets quite messy quite
| fast).
|
| Such a stark tradeoff doesn't need to exist! In theory
| STM gives you exactly the granularity you need where you
| can access the keys that you need atomicity for and only
| those keys together while allowing concurrent writes to
| anything else that doesn't touch those keys (this is
| exactly how e.g. the stm-containers library for Haskell
| works that's linked elsewhere).
| dwohnitmok wrote:
| No a TVar isn't a mutex guard. As a sibling comment points out
| it gives you transactional semantics similar to most relational
| databases.
|
| Here's an example in perhaps more familiar pseudocode.
| var x = "y is greater than 0" var y = 1
| forkAndRun {() => y = y - 1 if (y <= 0) {
| x = "y is less than or equal to 0" } }
| forkAndRun {() => y = y + 1 if (y > 0) {
| x = "y is greater than 0" } }
|
| In the above example, it's perfectly possible, depending on how
| the forked code blocks interact with each other, to end up with
| x = "y is less than or equal to 0" y = 1
|
| because we have no guarantee of atomicity/transactionality in
| what runs within the `forkAndRun` blocks.
|
| The equivalent of what that Haskell code is doing is replacing
| `var` with a new keyword `transactional_var` and introducing
| another keyword `atomically` such that we can do
| transactional_var x = "y is greater than 0"
| transactional_var y = 1 forkAndRun {
| atomically {() => y = y - 1 if (y <= 0) {
| x = "y is less than or equal to 0" } }
| } forkAndRun { atomically {() =>
| y = y + 1 if (y > 0) { x = "y is greater
| than 0" } } }
|
| and never end up with a scenario where `x` and `y` disagree
| with each other, because all their actions are done atomically
| together and `x` and `y` are specifically marked so that in an
| atomic block all changes to the variables either happen
| together or are all rolled back together (and tried again),
| just like in a database.
|
| `transactional_var` is the equivalent of a `TVar` and
| `atomically` is just `atommically`.
| mrkeen wrote:
| Mutexes lock _code_ , TVars lock _data_.
|
| If you lock a section of code (to protect data), there's no
| guarantee against mutations of that data from other sections of
| code.
|
| If you lock the data itself, you can freely pass it around and
| anyone can operate on it concurrently (and reason about it as
| if it were single-threaded).
|
| It's the same approach as a transactional database, where you
| share one gigantic bucket of mutable state with many callers,
| yet no-one has to put acquire/release/synchronise into their
| SQL statements.
| ghusbands wrote:
| As siblings note, TVar is a transactional variable. However,
| it's not just protective against concurrent writes but also
| against concurrent reads of altered variables, so it offers
| true atomicity across any accessed state in a transaction.
|
| So if you have a thread altering `foo` and checking that
| `foo+bar` isn't greater than 5 and a thread altering `bar` and
| checking the same, then it's guaranteed that `foo+bar` does not
| exceed 5. Whereas if only write conflicts were detected (as is
| default with most databases) then `foo+bar` could end up
| greater than 5 through parallel changes.
| jes5199 wrote:
| I love Haskell because I can write provably correct code that
| still doesn't work
| declan_roberts wrote:
| I love it because I can spend all my time noodling over types
| and never ship a product that would have been great shipped in
| a late night wine-fueled session of 1999 PHP.
| anonzzzies wrote:
| Now you would vibe it and ship it during the ol' drink.
| kqr wrote:
| If that is what you want to do, you can do that in any
| language. It's just that when you do it in e.g. Java, you have
| to spend a lot longer proving correctness before discovering
| that it doesn't work.
| jiggawatts wrote:
| A Haskell quote I like is: "I've only proven this correct, I
| haven't tried it."
| neilwilson wrote:
| Isn't that one of Dijkstra's supposed comments?
| OskarS wrote:
| It's Knuth! [1]
|
| "Beware of bugs in the above code; I have only proved it
| correct, not tried it."
|
| [1]: https://www-cs-faculty.stanford.edu/~knuth/faq.html
| jiggawatts wrote:
| It's incredible that given how fuzzy and inaccurate human
| memory is, we treat any LLM that can't perfectly recite
| volumes of information as somehow _beneath_ us.
| michalsustr wrote:
| I'm not familiar with Haskell concurrency. The combination of
| green threads and large memory allocations due to immutable data
| structures sounds like it would be hard to implement a web server
| handling 10k+ concurrent requests on commodity hardware?
|
| Btw. too bad author talks about microsecond guarantees usage but
| does not provide a link, that would be interesting reading.
| stevan wrote:
| > Warp is a high-performance HTTP server library written in
| Haskell, a purely functional programming language. Both Yesod,
| a web application framework, and mighty, an HTTP server, are
| implemented over Warp. According to our throughput benchmark,
| mighty provides performance on a par with nginx.
|
| Source: https://aosabook.org/en/posa/warp.html
| nesarkvechnep wrote:
| You obviously haven't ran anything on the BEAM (Erlang's VM).
| michalsustr wrote:
| Correct. Erlang also uses green threads?
| jlouis wrote:
| Yes. And immutable data structures.
|
| When data is immutable, it can be freely shared. Changes to
| the data essentially uses copy-on-write. And it only writes
| the delta change, since you don't need a deep copy due to
| immutability. Add that the garbage collectors of Haskell
| and Erlang are designed to work with a high allocation rate
| and have 0 cost for dead data, and this is much faster than
| what people think.
|
| The way you implement a webserver in either Haskell or
| Erlang is rather trivial. Whenever there's an incoming
| request, you make a thread to handle it. So you don't have
| 1 webserver serving 10k requests. You have 10k webservers
| serving 1 request each. And since they are started from the
| same core data, they'll share that due to immutability. See
| also old-style Apache or PHP and fork().
| eru wrote:
| Web servers handling lots of small requests are actually
| pretty easy to garbage collect to: you just delete all
| the data at the end of the request.
|
| Either you have a specialised GC that works like this, or
| probably a good general generational GC can pick up on
| this pattern on its own.
| jlouis wrote:
| Or you do as Erlang's BEAM VM: each thread has it's own
| memory area which is GC'ed individually. This means upon
| request termination, you just terminate the thread and
| the memory is reclaimed with no need for a GC.
| _jackdk_ wrote:
| The interaction of laziness and purity means that the memory
| costs are not always what you think. Purity means that it's a
| lot safer to share structure between old and new versions of a
| data structure where an imperative language would have to do
| defensive copying, and laziness means that you can
| incrementally amortise the cost of expensive rebalancing
| operations (Okasaki is the standard reference for this).
| cosmic_quanta wrote:
| > sounds like it would be hard to implement a web server
| handling 10k+ concurrent requests on commodity hardware?
|
| In practice, it is not. The canonical Haskell compiler, GHC, is
| excellent at transforming operations on immutable data, as
| Haskell programs are written, into efficient mutations, at the
| runtime level. Also, since web development is quite popular in
| the Haskell community, lots of people have spent many hours
| optimizing this precise use-case.
|
| In my experience, the real downside is that compilation times
| are a bit long -- the compiler is doing a LOT of work after
| all.
| eru wrote:
| > The canonical Haskell compiler, GHC, is excellent at
| transforming operations on immutable data, as Haskell
| programs are written, into efficient mutations, at the
| runtime level.
|
| Yes, at the level of native machine code and memory cells,
| there's not that much of a difference between immutability +
| garbage collection, and higher level source code that
| mutates. Thanks to GC you are going to overwrite the same
| memory locations over and over again, too.
| internet_points wrote:
| https://www.oreilly.com/library/view/parallel-and-concurrent...
| is a great resource for those who want to go deeper into this
| alatriste wrote:
| I read that book many years ago, but I haven't looked into
| Haskell for a long time. Is it still relevant today? I imagine
| many things have changed in 12 years!
| valcron1000 wrote:
| The fundamentals are the same, and `async` is as relevant as
| it was back then. The ecosystem is extremely stable in that
| regard.
| cosmic_quanta wrote:
| The author is thinking of updating the book to a second edition
| as well. Looking forward to it
| ahel wrote:
| noice
| thih9 wrote:
| After reading the title I was expecting a "pick two". From my
| anecdotal experience haskell is usually far from simple, but
| other configurations are possible too.
| wodenokoto wrote:
| I don't know how async is in other languages but I find Pythons
| async incredibly difficult to use, and I kinda feel validated
| about how poor chatGPT is at it as well.
|
| Is it because it is just a very hard thing, or is it because its
| a synchronous language, with async bolted on? (I'm talking about
| a purely language point of view, not from a python VM / GIL point
| of view)
| aeonik wrote:
| The easiest language I've used for async is Clojure--mostly
| because the language is immutable by default and ~99% of the
| code is referentially transparent. That doesn't magically solve
| async, but it removes an entire class of headaches by nudging
| you away from shared state and side effects. You don't need
| locks if there's nothing to lock.
|
| Async is hard, no doubt--but some languages are designed to
| reduce the surface area of what can go wrong. I've heard great
| things about Erlang, Elixir, and BEAM-based languages in
| general. They treat async not as an add-on, but as a core
| architectural principle.
| Starlevel004 wrote:
| It's because ``asyncio`` is a dogwater library that's barely
| functional and full of footguns. The ecosystem is about the
| same quality too.
| gpderetta wrote:
| Indeed, as much as I dislike async in general, asyncio is its
| own special hell.
| _jackdk_ wrote:
| From the footnotes:
|
| > It gets weirder: in Haskell, exceptions can be thrown to other
| threads!
|
| What's really interesting is that because of purity, you have to
| have asynchronous exceptions otherwise you give up a lot of
| modularity. At least that's what Simons Marlow and Peyton Jones
| argue in Asynchronous Exceptions in Haskell (2006):
| https://www.microsoft.com/en-us/research/wp-content/uploads/...
|
| > While the semi-asynchronous approach avoids breaking
| synchronization abstractions, it is non-modular in that the
| target code must be written to use the signalling mechanism.
| Worse still (for us), the semi-asynchronous approach is simply
| incompatible with a purely-functional language, such as
| Concurrent Haskell. The problem is that polling a global flag is
| not a functional operation, yet in a Concurrent Haskell program,
| most of the time is spent in purely-functional code. On the other
| hand, since there is absolutely no problem with abandoning a
| purely-functional computation at any point, asynchronous
| exceptions are safe in a functional setting. In short, in a
| functional setting, fully-asynchronous exceptions are both
| necessary and safe -- whereas in an imperative context fully-
| asynchronous exceptions are not the only solution and are unsafe.
|
| If you can read PLTese, it's really quite a nice paper.
| kamalhm wrote:
| Oh so this could be the inspiration on Virtual Threads and
| Structured Concurrency in Java
| cosmic_quanta wrote:
| My favourite thing about Haskell concurrency is that there are no
| colored functions [0]. Writing code in IO, or Async, or the next
| big thing (asychronous higher-order effect system of the
| future??), doesn't require language support like Python or Rust.
|
| The one construct that unlocks this lack of colored functions,
| STM, did require runtime support (as opposed to language
| support), which at least is transparent to downstream developers
|
| [0]: https://journal.stuffwithstuff.com/2015/02/01/what-color-
| is-...
| grandempire wrote:
| This is also an advantage of blocking code. It's just regular
| code. The async stuff is handled by the operating system.
| mrkeen wrote:
| Coloured functions are a feature - not a bug, Haskell is full
| of them, and they are exactly what make STM safe in Haskell,
| but abandonware in other languages which have tried.
| 2. The way you call a function depends on its color.
|
| `<-` or `>>=` vs `=` 3. You can only call a red
| function from within another red function.
|
| This should sound pretty familiar! You can only call an IO
| function from within another IO function. STM in this case
| makes a third colour: IO can call IO functions.
| IO can call STM functions. (*) IO can call pure
| functions. STM can call STM functions. STM can
| call pure functions. pure functions can call pure
| functions.
|
| (*) calling into an STM block from IO is what makes it 'happen
| for real': it's the `atomically` which has type STM a -> IO a.
|
| Having these coloured functions is what made STM achievable
| back in the mid-late 2000s, since the mechanism to prevent STM
| or pure functions from calling IO was already in-place.
|
| Other languages either tried to figure out how to contain the
| side-effects and gave up, or just released STM and put the onus
| on the user not to use side effects.
| kookamamie wrote:
| > 1. Compose the program into several threads of execution,
| traditionally scheduled and ran by the operating system
|
| The step 0 is missing:
|
| Compose the program into several lanes of execution,
| traditionally executed via SIMD.
|
| This is a massive piece of performance left on the table on
| modern computer architectures, by assuming threading is the first
| manifestation of concurrency.
| jayd16 wrote:
| SIMD has been somewhat of a massive failure in this regard.
| Unlike threads, most languages seem to ignore its existence and
| abdicate its usage to the sufficiently complex compiler.
|
| I wish there was better author time feedback to the developer
| on where they're getting such a perf boost. As far as I'm aware
| there's no popular linting or blue squiggle to guide you in the
| right direction.
|
| In games it seems like the popular pattern is to rewrite
| everything entirely in an entity component system framework.
| kookamamie wrote:
| Agreed completely. Most auto-vectorization approaches are
| hit-miss and you still cannot have big-binaries, where
| instruction set is decided dynamically, trivially.
|
| ISPC comes close, but does come with a learning curve.
| thuanao wrote:
| Correct? Anyone who has worked with concurrency in Haskell is
| probably laughing... :)
|
| Haskell's IO type system doesn't model concurrency at all. `IO a`
| could be a fork and join, an infinite event loop, really
| anything, it's a black box in terms of "correctness". Better than
| using JavaScript maybe, but hardly "correct" in any sort of
| formal, tractable sense.
| throwaway17_17 wrote:
| From the footnotes:
|
| > In bare-metal embedded systems or inside the operating system,
| it's not unusual to manually break computation into state
| machines, driven by interrupts.
|
| Although not the topic of TFA, in fact, the footnotes that this
| is "a whole different ball game." Does anyone have any good
| source for this aspect of 'low-level'/OS development? I'm more
| than capable of chasing down sources from a more high level
| introduction or overview, so anything would be helpful. This
| concept seems like it may just be a more pragmatic description of
| embedded/OS development than what I've read previously.
| ackfoobar wrote:
| "Fast" in title. But I don't see any benchmarks, or discussion on
| how to reason about the performance of STM code.
___________________________________________________________________
(page generated 2025-04-17 23:01 UTC)