Live data from Hacker News

Concurrency in Haskell: Fast, Simple, Correct

bitbashing.io

121–129 of 129 posts

Re: Concurrency in Haskell: Fast, Simple, Correct

#121
post #88

Earlier quoted context omitted.

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 popul…

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.

I would say that Highway [1] comes close. Can't say anything about ISPC because in gamedev work it never even came into consideration for multiple platforms.

1. https://google.github.io/highway/en/master/

Re: Concurrency in Haskell: Fast, Simple, Correct

#122
post #101

Earlier quoted context omitted.

>> 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]

Not sure what you are pointing out, so let me spell out what I said earlier. 1. You get single-writer multiple reader for free from the type system, without any runtime overhead. 2. For the same reason, the type system does not allow multiple writers. If you want multiple writers, then you are forced to use locks. Once you use locks, the runtime guarantees safety for this case. Either way, you get 100% safety.

You're not adding any new information. I already understand you completely.

> 1. You get single-writer multiple reader for free from the type system, without any runtime overhead.

Take the example from the article which is accepted by the Haskell type system:

   writeTBCQueue :: TBCQueue a -> a -> STM ()
   writeTBCQueue q v = do
     stillOpen 
Rust would reject this because of multiple writers.

Also, thinking about it more, I'm now very skeptical of Rust even providing 'single-writer multiple-reader'. Is it in fact single-reader-writer xor multiple-reader? In other words, how does it handle a goblin constantly moving money between accounts while a gnome is constantly trying to count the total amount?

  goblinBankerThread = forever $ do
    seed  (amount acctB)
            then moveAmount $5 acctA acctB
            else moveAmount $5 acctB acctA

  gnomeAccountantThread = forever $
    atomically $ do
      accounts 
Yes, Rust is 100% safe because it would reject this code, so it would never run. Not running code also has guaranteed no-overhead!

2. For the same reason, the type system does not allow multiple writers. If you want multiple writers, then you are forced to use locks

* Locks are problematic, which is why I chose STM over locks in the first place.

* Locks are in all the languages. Does your comment about 100% safety really apply to all languages ?

Re: Concurrency in Haskell: Fast, Simple, Correct

#123
post #122

Earlier quoted context omitted.

Not sure what you are pointing out, so let me spell out what I said earlier. 1. You get single-writer multiple reader for free from the type system, without any runtime overhead. 2. For the same reason, the type system does not allow multiple writers. If you want multiple writers, then you are forced to use locks. Once you use locks, the runtime guarantees safety for this case. Either way, you get 100% safety.

You're not adding any new information. I already understand you completely. > 1. You get single-writer multiple reader for free from the type system, without any runtime overhead. Take the example from the article which is accepted by the Haskell type system: writeTBCQueue :: TBCQueue a -> a -> STM () writeTBCQueue q v = do stillOpen Rust would reject this because of multiple writers. Also, thinking about it more, I'…

Your first point is not comparing the same thing. STM is wonderful, but as you no doubt know, it is meant for many TVars to be read/modified. This necessarily has overhead (transactional logs), performs poorly under contention and also is subject to livelock, and has no fairness.

In your goblin example, I believe the gnomeAccountantThread would have to constantly retry, because the writer (if successful) would have produced a new version of two accounts, which would trip up the reader, forcing it to start again. In general, Haskell's STM is built for short-lived transactions; for longer running transactions or those that touch a lot of objects, you'd need something like multi-versioned objects seen in databases or epochs to get a consistent snapshot. Neither Rust nor Haskell is suited to this example out of the box.

For your second question, you assume axiomatically that locks are problematic. They aren't in Rust (except, see later about deadlocks). Unlike any other language with in-place mutation, Rust will force you to use a mutex in order to share something for read-write (in a multiple writer scenario), otherwise it won't compile. You have to use lock() in order to get access to the underlying object, and once you have that object, the type system makes sure only the owner can mutate it. In C/C++/Java/Go, you don't get this guarantee at all ... it is possible to mistakenly use the object without using a mutex. So, there is not guarantee of safety in the other languages. There is a 100% guarantee in Rust.

---

That said, the problematic part about locks (whether it is mutexes or MVars in Haskell) is deadlocks, which is solved by having a deterministic lock order. In your Haskell example, if acctA and acctB were MVars, you'd do

    let (first, second) = if acctA 
         withMVar second $ \_ -> do
            ...

Re: Concurrency in Haskell: Fast, Simple, Correct

#124
post #105
post #79

Earlier quoted context omitted.

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.

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.

In the abstract, this is very similar to spawning a unix process for every request, never free-ing any memory, and letting the memory allocation die with the process.

Re: Concurrency in Haskell: Fast, Simple, Correct

#125
post #97

Earlier quoted context omitted.

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.

That’s incorrect. Only refs+dosync use stm. https://clojure.org/reference/refs Not atoms. From Hickey’s History of Clojure paper: “ Taking on the design and implementation of an STM was a lot to add atop designing a programming language. In practice, the STM is rarely needed or used. It is quite common for Clojure programs to use only atoms for state, and even then only one or a handful of atoms in an entire program.…

Haha, I read The Joy of Clojure way back in 2013 and conflated the different reference types with STM. So thanks for mentioning that, I always thought it weird that you'd need STM for vars and atoms too.

That said, I have never used a ref, nor seen one in use outside of a demo blogpost.

Re: Concurrency in Haskell: Fast, Simple, Correct

#126
post #100

Earlier quoted context omitted.

There's no time like the present. Feel free to reach out if I can help you along your journey

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 retu…

The manual string conversions are annoying. But with https://hackage.haskell.org/package/string-conversions-0.4.0... you just add a `cs` and don't think more about it.

Re: Concurrency in Haskell: Fast, Simple, Correct

#127
post #100

Earlier quoted context omitted.

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 retu…

The manual string conversions are annoying. But with https://hackage.haskell.org/package/string-conversions-0.4.0... you just add a `cs` and don't think more about it.

Oooh this is very nice!

Re: Concurrency in Haskell: Fast, Simple, Correct

#128
post #125

Earlier quoted context omitted.

That’s incorrect. Only refs+dosync use stm. https://clojure.org/reference/refs Not atoms. From Hickey’s History of Clojure paper: “ Taking on the design and implementation of an STM was a lot to add atop designing a programming language. In practice, the STM is rarely needed or used. It is quite common for Clojure programs to use only atoms for state, and even then only one or a handful of atoms in an entire program.…

Haha, I read The Joy of Clojure way back in 2013 and conflated the different reference types with STM. So thanks for mentioning that, I always thought it weird that you'd need STM for vars and atoms too. That said, I have never used a ref, nor seen one in use outside of a demo blogpost.

Totally! They are rare. Cheers

Re: Concurrency in Haskell: Fast, Simple, Correct

#129
post #86

Earlier quoted context omitted.

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. ` >=` 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…

It is a shame that the people you are answering is being downvoted, I also understand the importance of coloring functions, but look at the examples that person put, python and rust. In those, executing a colored function (at least the async related ones) propagates up to the top of the program, that is a cost that we have to interiorize, but I would be lying if I told you I wouldn't he happy with such behavior. I do…

A lot of people want to "inline" IO from a non-IO function too.

I'm glad that the fight happens between the developer and the compiler so that it doesn't have to happen between developers on every single pull request.

Post reply on HN