# Thoughts on monad-par

**URL:** <https://discourse.haskell.org/t/thoughts-on-monad-par/11233>\
**Category:** Learn\
**Created:** [January 18, 2025, 10:41pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233 "2025-01-18T22:41:29Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![jeukshi](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jeukshi/32/4040_2.png) [@jeukshi](https://discourse.haskell.org/u/jeukshi)\
**Post date:** [January 18, 2025, 10:41pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/1 "2025-01-18T22:41:29Z")

</div>

I’m thinking about implementing an API for parallel programming for [Bluefin](https://hackage.haskell.org/package/bluefin). Effect system seems like a nice home for the idea.

But.

There exist [monad-par](https://hackage.haskell.org/package/monad-par) package that does this. I vaguely remember it being a poster child for Haskell, look what Haskell can do! Nowadays, I rarely see it being mentioned.

Are you using `monad-par` (or some alternative)? Why/why not? What would make you use it?

I’ll answer first. I have never used `monad-par`. I just don’t think about using it. It is a combination of, me mostly shoving stuff around in IO, not caring that much about performance, parallelism requiring benchmarks, laziness. I also suspect that giving it some thoughts and being better integrated into my main monad stack (or effect system) might change this.

---

<div class="post-metadata">

**Author:** ![atravers](https://avatars.discourse-cdn.com/v4/letter/a/45deac/32.png) [@atravers](https://discourse.haskell.org/u/atravers)\
**Post date:** [January 20, 2025, 1:04pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/2 "2025-01-20T13:04:26Z")

</div>

Having briefly looked at the current [documentation](https://hackage.haskell.org/package/monad-par-0.3.6/docs/Control-Monad-Par.html) and original [research article](https://web.archive.org/web/20111119104238/http://www.cs.indiana.edu/~rrnewton/papers/haskell2011_monad-par.pdf), I’ve noticed a similarity to the advent of `seq` in Haskell 1.3 - it originally was a method of the `Eval` type class, so any definition which tried using `seq` generically would have `Eval` appear in its context. Likewise for `newFull`, `put` and `spawn`:

```haskell
newFull :: NFData a => a -> Par (IVar a)
put :: NFData a => IVar a -> a -> Par ()
spawn :: NFData a => Par a -> Par (IVar a)

```

with `NFData` needing to be added to the type-signature contexts of generic callers (or providing instances instead).

It seems reasonable…but it didn’t work _“in the large”_ for `Eval`:

> [@](#):
>
> (page 33 of 55)
> 
> Inspired by the Fox project at CMU, two of Hughes’s students implemented a TCP/IP stack in Haskell, making heavy use of polymorphism in the different layers. Their code turned out to contain serious space leaks, which they attempted to ﬁx using `seq`. But whenever they inserted a call of `seq` on a type variable, the type signature of the enclosing function changed to require an `Eval` instance for that variable—just as the designers of Haskell 1.3 intended. But often, the type signatures of very many functions changed as a consequence of a single `seq`. This would not have mattered if the type signatures were inferred by the compiler—but the students had written them explicitly in their code. Moreover, they had done so not from choice, but because Haskell’s monomorphism restriction _required_ type signatures on these particular deﬁnitions (Section 6.2). As a result, each insertion of a `seq` became a nightmare, requiring repeated compilations to ﬁnd affected type signatures and manual correction of each one. Since space debugging is to some extent a question of trial and error, the students needed to insert and remove calls of `seq` time and time again.
> 
> [A History of Haskell](https://www.iro.umontreal.ca/~monnier/2035/history.pdf)

…and looking at [all of its instances](https://hackage.haskell.org/package/monad-par-0.3.6/docs/Control-Monad-Par.html#t:NFData) (over 100 of them!) listed in `monad-par` alone, relying on `NFData` so much must also be laborious at times, having to define extra instances for `NFData` or add it to contexts far and wide merely to use `newFull`, `put` or `spawn`.

---

<div class="post-metadata">

**Author:** ![Bodigrim](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/bodigrim/32/1457_2.png) [@Bodigrim](https://discourse.haskell.org/u/Bodigrim)\
**Post date:** [January 20, 2025, 8:58pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/3 "2025-01-20T20:58:02Z")

</div>

@atravers you make it sound as if `NFData` is something peculiar, specific to `monad-par` and not a cornerstone type class, instances of which are provided by pretty much every library out there.

---

<div class="post-metadata">

**Author:** ![atravers](https://avatars.discourse-cdn.com/v4/letter/a/45deac/32.png) [@atravers](https://discourse.haskell.org/u/atravers)\
**Post date:** [January 20, 2025, 10:35pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/4 "2025-01-20T22:35:24Z")

</div>

It’s peculiar much like the `Strict` type class was in [pH](https://csg.csail.mit.edu/projects/languages/ph.shtml), as I dimly recall - the type system was being used to work around a limitation of the implementation; in pH, `Strict` was being used like the old `Eval` class - to constrain polymorphism.

For `NFData`, the deficiency is more operational: Haskell doesn’t have a primitive hyperstrict-evaluation function (_e.g._ `compel :: a -> a`) like Miranda™’s `force`.

Returning to theory: I once defined a `Monomo` type class:

```haskell
newIORef :: Monomo a => a -> IO a -> IO (IORef a)
readIORef :: Monomo a => IORef a -> IO (IORef a)
writeIORef :: Monomo a => IORef a -> IO a -> IO ()

```

to work around not being able to use Haskell’s monomorphism restriction directly with mutable references in an attempt to solve the problem of polymorphic references (that once afflicted Standard ML). Just like `Eval`, it too started to be laborious _“in the large”_, with me having to placate GHC by adding instances here, there and elsewhere - something like:

```haskell
newIORef :: monomo a . a -> IO a -> IO (IORef a)
readIORef :: monomo a . IORef a -> IO (IORef a)
writeIORef :: monomo a . IORef a -> IO a -> IO ()

```

would have been simpler.

So if `compel :: a -> a` did appear soon in a version of GHC, it would be interesting to see how many of those instances of `NFData` now provided by pretty much every library out there would continue to exist.

---

<div class="post-metadata">

**Author:** ![jeukshi](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jeukshi/32/4040_2.png) [@jeukshi](https://discourse.haskell.org/u/jeukshi)\
**Post date:** [January 21, 2025, 5:13pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/5 "2025-01-21T17:13:18Z")

</div>

@atravers Thanks. I understand your point, but on the other hand, what can be done in Haskell today?

I see `NFData` as a helpful interface, so that the library can force threads to do the actual work, instead of returning thunk, making threading pointless. But it is a choice, that can be left for users to figure out. And to be fair, there are NFData-less variants of functions in `monad-par`. Yet none of their users are here 🙂 .

---

<div class="post-metadata">

**Author:** ![atravers](https://avatars.discourse-cdn.com/v4/letter/a/45deac/32.png) [@atravers](https://discourse.haskell.org/u/atravers)\
**Post date:** [January 21, 2025, 7:09pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/6 "2025-01-21T19:09:21Z")

</div>

In your opening post, you asked these questions:

1. 

> Are you using `monad-par`[…]?

2. 

> […] why not?

3. 

> What would make you use it?

To clarify my previous responses:

1. No.

2. Because of the need to either:

3. An alternative to `NFData` (such as my suggestion - `compel :: a -> a`, a primitive hyperstrict-evaluation function), much the same way an independent primitive `seq` was the chosen alternative for `Eval` in Haskell 98.

* * *

Now to respond to your subsequent post:

- 

> […] what can be done in Haskell today?

Without something like `compel`, `NFData` is probably the least-worst option.

- 

> I see `NFData` as a helpful interface, so that the library can force threads to do the actual work, instead of returning thunks, making threading pointless.

`Eval` was also intended to be helpful.

- 

> But it is a choice, that can be left for users to figure out.

From what I’ve read, there was nothing preventing users of `Eval` from defining a library module for it and using that instead of the primitive `seq`. But most chose the new primitive.

- 

> And to be fair, there are NFData-less variants of functions in `monad-par`.

But those variants are only head-strict - _“tail-thunks”_ would be left unevaluated, thereby making threading _“partially-pointless”_.

---

<div class="post-metadata">

**Author:** ![eldritch-cookie](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/eldritch-cookie/32/4130_2.png) [@eldritch-cookie](https://discourse.haskell.org/u/eldritch-cookie)\
**Post date:** [January 22, 2025, 2:18pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/7 "2025-01-22T14:18:23Z")

</div>

this library is implementing parallelism for pure computations?  
i thought the threaded runtime already did that?  
why would i want to micromanage my program’s execution?

---

<div class="post-metadata">

**Author:** ![atravers](https://avatars.discourse-cdn.com/v4/letter/a/45deac/32.png) [@atravers](https://discourse.haskell.org/u/atravers)\
**Post date:** [January 22, 2025, 11:09pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/8 "2025-01-22T23:09:17Z")

</div>

> why would I want to micromanage my program’s execution?

Why indeed:

> [@](#):
>
> (pages 18-19 of 217)
> 
> A parallel implementation is free to do the applications simultaneously. This no-intervention-needed parallelism is usually called _implicit parallelism_ and it is very important for scalable machines: one cannot expect a programmer to pre-plan the execution of hundreds of thousands of independent processing tasks “by hand.”
> 
> [Graph reduction without pointers](https://www.cs.unc.edu/techreports/89-045.pdf) (1989).

But the threaded runtime system of GHC doesn’t (yet) work like that - it relies instead on the appearance of calls to `par` (and sometimes `pseq`) in Haskell sources, which can be tedious to _always_ use correctly:

> [@](#):
>
> (page 2 of 12)
> 
> […] difﬁculties arise when we want to be able to program parallel algorithms with these mechanisms. To use `par` effectively, the programmer must
> 
> (a) pass an _unevaluated computation_ to `par`,
> 
> (b) ensure that its value will not be required by the enclosing  
> computation for a while, and
> 
> (c) ensure that the result is _shared by the rest of the program_.
> 
> If either (a) or (b) are violated, then little or no parallelism is achieved. If (c) is violated then the garbage collector may (or may not) garbage-collect the parallelism before it can be used. We often observe both expert and non-expert users alike falling foul of one or more of these requirements.  
> These preconditions on `par` are _operational_ properties, and so to use `par` the programmer must have an operational understanding of the execution — and that is where the problem lies. Even experts ﬁnd it difﬁcult to reason about the evaluation behaviour, and in general the operational semantics of Haskell is undeﬁned.
> 
> [A Monad for Deterministic Parallelism](https://web.archive.org/web/20111119104238/http://www.cs.indiana.edu/~rrnewton/papers/haskell2011_monad-par.pdf)

---

<div class="post-metadata">

**Author:** ![konsumlamm](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/konsumlamm/32/3193_2.png) [@konsumlamm](https://discourse.haskell.org/u/konsumlamm)\
**Post date:** [January 23, 2025, 10:11am UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/9 "2025-01-23T10:11:21Z")

</div>

I don’t think using `NFData` is a problem in practice and I’ve never seen anyone complain about it before. One advantage of it being a class is that implementations can keep thunks that are used by the structure of the type (one example being `Seq`). The reason `monad-par` uses `NFData` is to avoid bugs where `runPar` only evaluates to WHNF, doing almost no work.

Even if `NFData` was a problem, I don’t think it would be fair to blame libraries for not using a nonexistant alternative (there’s not even a proposal for it!).

> [@jeukshi](#):
>
> Are you using `monad-par` (or some alternative)? Why/why not? What would make you use it?

I’ve used it a bit, but I found it a bit unergonomic that everything is wrapped in a `Par` monad, so you can’t easily compose it with pure functions. In my micro-benchmarks I also found it to be quite slow, but take that with a grain of salt. An alternative is `parallel`, which provides “strategies” that give you precise control over how and when to evaluate your data. It relies on laziness instead, so you can do something like `x `par` f x` which will start evaluation of `x` in parallel, until it is needed by `f`. This also makes it harder to get right though. It also provides an `Eval` monad that has a similar interface to `Par`.

However, parallelism has an overhead, so it often actually makes your program slower. It’s hard to know when exactly it provides a speedup. Another approach to parallelism is `massiv`, a multi-dimensional array library, that automatically parallelizes most of its operations (this works much better IME).

Automatically letting the compiler parallelize your pure functions seems like a really bad idea, due to the reasons above.

---

<div class="post-metadata">

**Author:** ![atravers](https://avatars.discourse-cdn.com/v4/letter/a/45deac/32.png) [@atravers](https://discourse.haskell.org/u/atravers)\
**Post date:** [January 23, 2025, 10:55am UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/10 "2025-01-23T10:55:56Z")

</div>

> I don’t think using `NFData` is a problem in practice and I’ve never seen anyone complain about it before.

And you still haven’t - my comments are not complaints but _observations_: there are certain similarities between the (old, pre-H.98) `Eval`, `Monomo` (by me), and `NFData` which I’ve noted.

* * *

> Even if `NFData` was a problem, I don’t think it would be fair to blame libraries for not using a nonexistent alternative (there’s not even a proposal for it!).

Proposals - you mean like this one:

> <https://github.com/ghc-proposals/ghc-proposals/pull/240>
>
> The \[proposal\](https://github.com/ghc-proposals/ghc-proposals/blob/master/propos…als/0052-threaded-by-default.rst) has been accepted; the following discussion is mostly of historic interest.
> 
> \---
> 
> Compile with \`-threaded\` by default. For those in need of the non-threaded RTS, provide the new \`-single-threaded\` flag.
> 
> \[Rendered\](https://github.com/ulysses4ever/ghc-proposals/blob/master/proposals/0000-threaded-by-default.rst).
> 
> \[Reddit thread\](https://www.reddit.com/r/haskell/comments/byvq5w/ghc\_proposal\_compile\_with\_threaded\_rts\_by\_default/).

…_“going nowhere fast”_ for over five years? Yeah, that’s working really well.

* * *

> Automatically letting the compiler parallelize your pure functions seems like a really bad idea […]

Some people still think that automatically letting the compiler manage the program’s heap memory is also a really bad idea. But the rest of us are willing to let the compiler do just that anyway.

Likewise for parallelism…elsewhere:

> [@](#):
>
> […] parallelism is the _default_ in ParaSail. It is our belief that that is the only way that programmers will learn to take full advantage of the _multicore_ world. That is, in ParaSail, it is more work to create _non_-parallel algorithms than to create parallel ones, so programmers, being fundamentally a lazy lot, will end up writing mostly parallel algorithms in ParaSail.
> 
> [Designing ParaSail, a new programming language: ParaSail’s implicit parallelism](https://parasail-programming-language.blogspot.com/2009/10/parasails-implicit-parallelism.html)

But this is starting to drift away from the original topic:

> [@jeukshi](#):
>
> - Are you using `monad-par` (or some alternative)?
> - Why/why not?
> - What would make you use it?

---

<div class="post-metadata">

**Author:** ![jmcarthur](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jmcarthur/32/3283_2.png) [@jmcarthur](https://discourse.haskell.org/u/jmcarthur)\
**Post date:** [January 23, 2025, 1:57pm UTC](https://discourse.haskell.org/t/thoughts-on-monad-par/11233/11 "2025-01-23T13:57:31Z")

</div>

> Are you using `monad-par` (or some alternative)?

I have never used `monad-par`, not even just to experiment with. These days I will occasionally use the `massiv` library or, less commonly but more happily, `par` and `seq`. Related to `par` and `seq` is the `parallel` package, which is more theoretically appealing to me than `monad-par`, but I have never actually had a reason to use it in anger. That’s about it as far as parallelism for me.

> Why/why not?

From the very beginning, I felt like its interface was too imperative for my tastes. Explicitly getting and setting from mutable references may be something I do occasionally, but parallelism is supposed to be something that purely functional programming is theoretically great for (although, admittedly, there is some misalignment between theory and practice so far), so ivars, especially with restrictions that result in runtime exceptions if you violate them, don’t hold much appeal for me.

> What would make you use it?

I think it is just not the abstraction I’m looking for, so anything that would make me use it would change it at such a fundamental level as not be `monad-par` anymore.

* * *

> I vaguely remember it being a poster child for Haskell, look what Haskell can do!

Although I do remember a bit of promotion for it, and some due attention simply because of its author’s reputation, I do not remember it ever holding “poster child” status. IMO, there are two (kind of weak) candidates as poster children for parallelism with Haskell:

- `par` (i.e., sparks)
- “data parallelism.” Although the exact library/research getting the most hype has changed a few times, I remember being very excited about the now-abandoned nested data parallelism research, and there have been some interesting libraries for non-nested parallelism, like `accelerate`, `repa`, and `massiv`, the latter being the one I currently view most favorably.

I know the topic here is parallelism, not concurrency, but I’ll add that I think there is a lot more convergence on what the poster children in the area of concurrency are. There are several, but I would say the ones that stand out the most are:

- GHC itself, for its excellent support for lightweight threads
- STM

There are other indispensable things in this space, but they feel a bit less unique to Haskell, so I would not go as far as to call them poster children.
