Looking for a library like `concurrency` but with file IO operations

For a demo I’m looking for a library like concurrency but the typeclass should include methods for opening files, and file descriptor operations (for reading, writing, seeking, flushing buffers etc.).

Does something like this exist? I’ve been searching in hackage and hoogle but no luck…

Thanks.

Are you strictly looking for a typeclass-based approach, or are you fine with using an effect system? effectful comes batteries-included with a bunch of effects, including filesystem-related ones.

1 Like

Effect systems are also fine, but I was hoping to use dejafu for testing my code written with concurrency. Does something like dejafu already exist for effectful?

Unfortunately, I don’t know anything of such for the effectful ecosystem. Though, now that I understand your use case a little bit more, maybe I can suggest taking a look into the io-sim package (and related set of libraries including io-classes). The package description states (emphasis mine):

io-sim together with io-classes is a drop-in replacement for the IO monad (with some ramifications). It was designed to write easily testable Haskell code (including simulating socket programming or disk IO)

1 Like

fs-sim: Simulated file systems might fit your needs

1 Like

Thanks for the suggestions. io-classes doesn’t have file IO. fs-api seems to be the file IO equivalent of it, except it doesn’t use a monad typeclass, instead uses a data type… (Why is the inconsistency? They seem to be developed by the same company.)

effectful seems to have it all and in one package. Perhaps that’s the way to go.

Anyway, thanks again!

If I could understand your use case then I’d be happy to add support to Bluefin. If you’re inclined to explain to me what you need then please do.

Probably a design choice. HasFS could indeed be a type class but Type Class <-> Record type, its not that much of a difference when it comes down to it. Passing a HasFS to your functions or requiring HasFS constraint (if it were a type class) is just a matter of preference.

A bit unrelated, but concurrency (and probably any other such library that does not follow upstream Control.Concurrent faithfully) is a questionable library to use in production.

Yes, you can check that some events are handled correctly, but that means you’re forever tied to a simplified copy of Control.Concurrent that hasn’t been updated since 2020.

This means that:

  • concurrency doesn’t have 5 years (and counting) worth of bugfixes
  • concurrency has a different behaviour from a stock concurrency libraries.

This last point is extremely dangerous.

For example, the stock concurrently from async uses throwTo ... AsyncCancelled, while the bare-bones copy of concurrently in concurrency uses killThread. It’s a slight but very significant change in a behavior that breaks code written for stock libraries.

I worked on a code “proven correct” by concurrently, but when I added a simple no-brainer code that worked perfectly with Control.Concurrent I was unpleasantly surprised to find that it was broken with concurrently as it has an outdated and broken behavior.

Think twice before using concurrently and dejafu. The good old engineering approach of writing the simplest possible concurrent code that is obviously correct (can be proven in your head without any model checking) may be a much better option for both correctness and maintainability.

killThread is just throwTo ... ThreadKilled, and it’s very worrying if changing AsyncCancelled to ThreadKilled can change behaviour. Neither of those exceptions should be caught by or interacted with by a thread in any way. They should just bubble up to the top and kill the thread. In what way did you see a difference in behaviour?

The stock link handles AsyncCancelled differently:

link ignores AsyncCancelled exceptions thrown in the other thread, so that it’s safe to cancel a thread you’re linked to.

The behaviour was changed in Ability to cancel a linked thread without killing self · Issue #25 · simonmar/async · GitHub after the concurrently development stopped.

I had something like this:

withAsync (threadDelay ... >> error "timeout") $ \ timeout -> do
  link timeout -- will kill the current thread on a timeout
  connectToSomeService
  ... 
  cancel timeout -- we're fine there, the timeout is no longer needed
  ...

But cancel killed the current thread (and I suspect exiting from withAsync may throw an unexpected exception as well).

IIRC, this whole withAsync ... link was created because of concurrently didn’t reimplement System.Timeout.timeout.

So it’s a combo: I can’t use the standard functions, and when I try to imitate them, I run into unexpected 5-year-old behaviour.

Very interesting! Thanks for explaining.

There are two possible consequences to this, and I’m not sure which you’re objecting to. (Maybe both.)


Firstly, concurrency's link doesn’t have the desirable behaviour. It’s nothing to do with the choice of ThreadKilled versus AsyncException, it’s just to do with whether it suppresses whatever exception it uses from being rethrown to the parent.


Secondly, if you killThread (via asyncThreadId) an async-linked thread, the ThreadKilled will be thrown to you when the linked thread terminates. That’s bad!

It can happen because Async is an abstract wrapper around a ThreadId (and result/exception). Having a ThreadId is a strong level of control over a thread! What would happen if we didn’t have

asyncThreadId :: Async a -> ThreadId

That sounds like it would be better encapsulation.


So, I’m not sure exactly what you’re objecting to, whether it’s firstly that the design of concurrency itself is lacking, or whether, secondly, it composes poorly with other libraries.

Having said that, perhaps “significant change in a behavior that breaks code written for stock libraries” means that code written against the async API won’t work when ported directly to the concurrency API.

Thanks!