[ANN][RFC] strict-mutable-base - strict variants of mutable data types from base

I’m preparing to release a new small library https://github.com/arybczak/strict-mutable/tree/master/strict-mutable-base that implements API for strict (WHNF) variants of Chan, IORef and MVar for proactive prevention of space leaks (links lead to the source code in the repo).

Basically, pretty much always when I need MVarS or IORefs in my code, I want strict modification, but

  • for MVar there are no such functions, so you either always have to write a utility function or remember to use $! or similar every time you modifyMVar, which is annoying and way too fragile.
  • for IORef there’s modifyIORef', but again, it’s far too easy to forget the ' and end up with a space leak manifesting itself later. writeIORef is also too lazy.

I wanted API that

  1. Guarantees that the data you store in MVar/IORef is always in WHNF.
  2. Has the simplest structure/deps possible.

Surprisingly there seems to be no package for this. The closest I found are

  • strict-concurrency, but it doesn’t use newtypes over base types (so no (1)) and requires NFData, which is IMO too strong. Also, judging from GH activity the package seems abandoned.
  • strict-mvar which is almost what I want, but it’s based on io-classes (and looks like it got deprecated recently) :disappointed:

The plan is to release strict-mutable-base, perhaps add a wrapper package for MonadUnliftIO/MonadBaseControl and also strict-mutable-stm for corresponding types in the stm package, since I need strict variants of these too, just less often.

However, maybe I’m missing something and there already exists such a package and I’m just not aware of it, in such case please let me know. Same if you have any comments about the API, better to take care of potential issues before the first release.

19 Likes

FTR: It was deprecated because it now is part of io-classes as a public sublibrary.

2 Likes

@arybczak This is great! Especially if it comes with an unliftio adapter.

Re. API design, I imagine it will take me some active practice to adopt it because of all the “primed” variants of the usual interfaces. Perhaps a typeclass based interface that lets you choose strict vs non-strict variants (like mtl does) according to which module you import would also make it easier to migrate. WDYT?

1 Like

If I dropped ticks from the API, I don’t think you need any typeclasses, you could just change the import of Control.Concurrent.MVar to Control.Concurrent.MVar.Strict etc. and that would be it.

I considered this design, but I chose to go with ticked variants because MVar/IORef/Chan is generally imported unqualified (unlike e.g. Text or Map) and then at use sites you can’t see whether operations are strict or lazy, which I find unsatisfactory.

With ticked API you can also use hlint in a CI to forbid usages of modifyMVar and modifyIORef for automated checking that lazy stuff doesn’t get through.

1 Like

I’m very much supportive of this idea. We need more strict variants of commonly-used functionality, so we can make invalid laziness unrepresentable.

7 Likes

FYI, it’s released :slightly_smiling_face:

6 Likes

Throughout my 7-year production Haskell experience working on web applications/services, all the space leaks I can remember were caused by a non-strict use of one of these types.

4 Likes

Here’s to seven better years in production!

8 Likes

Alternatively, the implementation of laziness GHC uses could be upgraded, thereby ending this “cottage industry” of strict duplicate types: so much less code to maintain…