Live data from Hacker News

Haskell's Missing Concurrency Basics (2016)

snoyman.com

11–20 of 27 posts

Re: Haskell's Missing Concurrency Basics (2016)

#11
I'm kind of surprised to hear that writing to stdout is a source of concurrency problems in a language that's considered to be functional.

Surely if you can pass your IO handles to all functions that need them, you can decide on a mutexing/buffering strategy at the top of your program, wrap the standard IO interface with a delegate that does so, and pass it on. Then, for all libraries called thereafter to use it consistently isn't just a no-brainer, it's an outright given, isn't it? There's no global (impure, non-functional) handle to stdout, is there?

Re: Haskell's Missing Concurrency Basics (2016)

#12

I'm kind of surprised to hear that writing to stdout is a source of concurrency problems in a language that's considered to be functional. Surely if you can pass your IO handles to all functions that need them, you can decide on a mutexing/buffering strategy at the top of your program, wrap the standard IO interface with a delegate that does so, and pass it on. Then, for all libraries called thereafter to use it cons…

Haskell's being functional is pretty much irrelevant here. The process has one stdout. If functions that write to file handles don't acquire a lock, then the output of different threads will get mixed up.

Re: Haskell's Missing Concurrency Basics (2016)

#13

I'm kind of surprised to hear that writing to stdout is a source of concurrency problems in a language that's considered to be functional. Surely if you can pass your IO handles to all functions that need them, you can decide on a mutexing/buffering strategy at the top of your program, wrap the standard IO interface with a delegate that does so, and pass it on. Then, for all libraries called thereafter to use it cons…

Being functional doesn't mean interaction with the outside world is going to lose its difficulty. Stdout being buffered basically means you have to sort out how to get consistent output just like in most other languages.

Even if you do as you say, you can still bypass it by writing to stdout directly outside of your stable buffering mechanism. But at that point the language isn't to be blamed here.

Re: Haskell's Missing Concurrency Basics (2016)

#14
post #4

How does erlang/elixir do it? I've never really had any problems.

They have a single process which manages IO, you communicate w/ it by passing messages. There is never any contention because it's never shared. Of course, there are trade-offs involved with this decision, but it's a really nice architecture.

Re: Haskell's Missing Concurrency Basics (2016)

#16
post #12

I'm kind of surprised to hear that writing to stdout is a source of concurrency problems in a language that's considered to be functional. Surely if you can pass your IO handles to all functions that need them, you can decide on a mutexing/buffering strategy at the top of your program, wrap the standard IO interface with a delegate that does so, and pass it on. Then, for all libraries called thereafter to use it cons…

Haskell's being functional is pretty much irrelevant here. The process has one stdout. If functions that write to file handles don't acquire a lock, then the output of different threads will get mixed up.

Maybe I'm confused, but isn't this expected, desired, behavior?

Re: Haskell's Missing Concurrency Basics (2016)

#18
post #12

I'm kind of surprised to hear that writing to stdout is a source of concurrency problems in a language that's considered to be functional. Surely if you can pass your IO handles to all functions that need them, you can decide on a mutexing/buffering strategy at the top of your program, wrap the standard IO interface with a delegate that does so, and pass it on. Then, for all libraries called thereafter to use it cons…

Haskell's being functional is pretty much irrelevant here. The process has one stdout. If functions that write to file handles don't acquire a lock, then the output of different threads will get mixed up.

It's easy to split hairs about what "being functional" means, but the way that Haskell implements IO is certainly a byproduct of this. In particular, my preferred mental model of Haskell doesn't include "functions that write to file handles"; but rather, these would be pure functions which return IO "actions".

This distinction is often inconsequential, but this case seems to rely on how we combine those "actions" together (which, due to laziness, may happen far away from where/when the functions are called; see 'lazy IO'). For sequential IO we can combine things with Applicative and Monad, which gives us a definite order, but using these in a concurrent setting would cause too much synchronisation and determinism to be useful. I've not done enough concurrent Haskell to know how the various alternatives stack up; although I did play with Arrow many years ago, before it fell out of favour (seemingly for Profunctors?).

Re: Haskell's Missing Concurrency Basics (2016)

#19
post #17

How can you get a performant language if your I/O granularity is 1 character? :-O EDIT: this is an honest question, I was shocked to read what was in the article.

Strings in Haskell are one of the language's sore points. It's something that's mostly a non-issue for those using Haskell day to day, but may be surprising to newcomers.

Haskell's built-in string type is a list of characters. This is mostly for historical reasons, but it's also handy in education (installing extra packages is a barrier for learners; list processing is common in introductory courses, but lists are polymorphic/generic in their element type; lists of characters are a nice concrete type, which follows on easily from "hello world"); also there are arguments about the theoretical elegance of linked lists, KISS for the builtins, whether there's concensus on what the best alternative is, etc.

Anyone who cares about Haskell performance will have hit this early on, and be using a different string implementation, as mentioned in the article. In particular there's ByteString for C-like arrays of bytes, and there's Text which is just a ByteString with extra metadata like character encoding. In fact, ByteString doesn't have to be a single contiguous array: it can be a list of "chunks", where each chunk contains a pointer to an array, an offset and a length; this speeds up many operations, e.g. we can append ByteStrings by adding chunks to the list (pointing to existing arrays), we can take substrings by manipulating the offsets and lengths, etc. This is all perfectly safe and predictable since the data is immutable, where other languages which allow mutation might prefer to make copies of the data to reduce aliasing.

The other aspect is the buffering mode of the handle, which is discussed a little in the article and its comments (e.g. line-based buffering, etc.).

Re: Haskell's Missing Concurrency Basics (2016)

#20
post #12

Earlier quoted context omitted.

Haskell's being functional is pretty much irrelevant here. The process has one stdout. If functions that write to file handles don't acquire a lock, then the output of different threads will get mixed up.

It's easy to split hairs about what "being functional" means, but the way that Haskell implements IO is certainly a byproduct of this. In particular, my preferred mental model of Haskell doesn't include "functions that write to file handles"; but rather, these would be pure functions which return IO "actions". This distinction is often inconsequential, but this case seems to rely on how we combine those "actions" tog…

I just don't see how monadic IO is relevant to this issue. putStrLn either acquires a lock or it doesn't. As it happens, it doesn't. It just as easily could (without any change in its type). The same issue arises in any language that supports threads.
Post reply on HN