What should I use for effect handling?

In my hobby projects, I am currently using MTL. It has become unwieldy as I have to create typeclass for each and every effect I have. The code duplication has me uneasy.

However, I heard that various effect systems are a havoc and generally not worth the effort. This scares me from trying a proper effect system. I also dislike large dependency footprints and complex machinery, which bars myself from common effect libraries.

Can I get recommendation on what I should use?

Alternatively… can I use global constant which require IO action to set up? That would ease my life so much! Lack of global variables have bothered me quite often…

Have a look at Härmel Nestra’s Purely functional global variables. But also remember this: GHC will eventually be multi-threaded by default

Oh, I guess I can utilize unsafePerformIO for this purpose. I am worried it might cause unexpected bug, how safe is it to use unsafePerformIO in this context?

What significance does GHC being threaded by default have? Even it is not threaded, you can still achieve concurrency (therefore potential race conditions) with forkIO.

Yes, GHC supports Concurrent Haskell by default. But without the multi-threaded RTS, it doesn’t work as well as people expect - see this thread for more details.

Perhaps the more important feature that would be provided is parallelism, because of the use of a certain unsafe kludge which allows:

  • an IORef to be defined at the top level;
  • the use of IO actions to be “camouflaged”.

I foresee more than a few difficulties in the future when people start using the parallel features of Haskell with definitions purporting to be free of observable effects, only to notice things no longer worked like they used to (if at all). Furthermore, it would make “helpful advice” provided in articles like this less so:

The thread you pointed to mainly talks about performance problems. How does this link to global variables defined by unsafePerformIO (newIORef ...) specifically?

Alternatively… can I use global constant which require IO action to set up? That would ease my life so much!

In what way would it be more convenient? How would that global variable help with effect handling?

1 Like

ReaderT (... IORefs ...) IO will get you 95% of the benefits of global variables without the drawbacks.

Because most of my “effects” are something like registry to register stuffs, configurations, or application-constant handle to APIs.

Have you tried just passing stuff in?

I suggest having a look at effectful.

It’s one of the newest libraries that largely fixes issues of older effect systems (for more info have a look at the readme in the above link). Also, effectful-core doesn’t have a lot of deps.

You can have a look at the tutorial for defining effects and decide for yourself if it’s worth it for your project (on in general).

Disclaimer: I’m the author.

5 Likes

…and the accepted proposal mentions the correctness issues e.g:

The presence of unsafely-hidden global state makes matters more complicated if definitions pretending to be free of observable effects which rely on that state are used across several threads.

I’m afraid I don’t understand. Could you more explicitly state how the quote relates to the problem you described, and provide an example of “definitions pretending to be free of observable effects” that rely on the state going wrong in a multithreaded setting?

2 Likes

Andrzej, do you have a collection of “what you can do with effectful examples”? I’d love to be able to show people how effectful compares to other approaches, particularly ReaderT ... IO and mtl, by way of worked example, the more real world the better.

That requires a huge record which contains everything, I dislike adding individual handle to the record and having to access them with field selector.

Also, this means there should be central Env module for the huge record, and every other action module should depend on it. I am afraid of potential cyclic dependency.

You can also pass separate arguments. Most functions are not going to depend on everything. What would work best depends on your particular case though. I prefer simplicity first, so I recommend trying the following: passing arguments manually, passing stuff around in ReaderT, effectful. Besides effectful I haven’t found a convincing effect system.

1 Like

Alongside with Andrzej I can recommend the usage of effectful-core. Really delightful ergonomics, and interfacing with mtl/transformers is integral part of the conception and the documentation.

4 Likes

this means there should be central Env module for the huge record, and every other action module should depend on it. I am afraid of potential cyclic dependency.

Cyclic dependencies are indeed noxious. And letting actions have knowledge of the entire environment badly increases coupling. But there’s an easy way out: have some auxiliary Has-like typeclass, and make actions work with a generic environment, requiring only the things they actually need.

I have a library built on this idea called dep-t, basically a coat of paint over the “handle pattern”. For an example, look at this Env-like type. It references every component in the application, but if you look at where Clock, Logger, Repository… are defined, they don’t depend on the environment at all.

Having a central place in your app that “knows everything” and assembles the application by wiring each loosely coupled component is actually a good idea. That place is sometimes called a composition root.

I dislike adding individual handle to the record

That may indeed might be a problem. It can be somewhat tedious and verbose work. One can resort to certain amounts of “magic” in order to alleviate it. The good news is that the magic will be limited to the composition root. The components themselves can be boring.

1 Like

For small examples, the tutorial for dynamic and static effects has a few (also, for dynamic dispatch there’s a direct comparison with mtl style definition and additional info how to integrate with existing mtl style effects).

There’s also:

  • a filesize benchmark that contains implementations for various effect libraries, in particular mtl (though it’s still quite simple).

  • an example how effectful integrates with conduit in this ticket.

As for the actual real world usage, I know that flora-server and monocle (a link to the PR is here, they also wrote a blog post about it) both switched to effectful from the ReaderT pattern.

5 Likes

Try using search terms like:

  • haskell shared mutable state
  • haskell "data race"

…the latter brought up this article for me:

…with its data-race-example program.

Now, as for an example of definitions only looking free of observable effects behaving badly in the presence of multiple parallel threads…my current 762Mibyte installation of GHC lacks Control.Parallel, so whatever code examples I could write up and show here would be untested, so I’ve decided not to - we need more working examples, not less.


Ensuring correctness of effects usage is difficult enough in an I/O context, so the absence of that context and the manifest ordering it provides makes matters even more challenging: