Have effect systems *completely* replaced transformers/MTL on your code?

It depends on what is required:

  • If you need the effects to be nested in some precise way, you can use monad transformers to build up the desired monadic type “layer by layer”.

  • If that level of detail isn’t needed, then you would probably opt for an effect system.

But this is just a heuristic - in reality, you would use what’s more appropriate for the current situation.

I second all what you’ve been saying there.

The only comment I would add is that, while I certainly enjoy and long for the “I make my own choice” culture in Haskell myself, I also would advocate for more visible and opinionated approaches by leaders. This way, people who are new to the community or prioritizing following over making own choices could find a way forward.

The whole ReaderT Design Pattern is a great example. We could continue to argue about which effect system is better. But someone needs to ultimately show the way.

And back to OP, I see a call for visible guidance in the community. There are lots of people are silent, and if they don’t see clarity, they leave and we will never see them again.

1 Like

To me anyway, the current proliferation of effect systems is much like the current proliferation of FRP/DCTP systems - what’s needed for both of them is that unifying insight (the proverbial “Aha! moment” ) upon which they (to varying extents) can all be based on. Of course, if those insights could lead to the formation of single “grand unified” systems for effects and FRP, that would be even better…

1 Like

Heh, I do love the learning experience listening to what Haskell Masters are talking about.

I am just conscious of the potential “leaving the new comer confused” scenario; while the masters are still figuring out things (which will be forever anyways…) I’d like to see more people using Haskell, but not wanting us to oversell things that are not ready neither. Not sure where the balance is, but I guess this is what this discourse is for.

This is a good question. If for testing benefits only, I would not be too impressed with effects systems. What got me hooked on them is not the dynamic dispatch property. Testability is nice, but not generally what I need to be the core architectural principle of my program.

What got me hooked on effects as THE way to design software in production, in short is:

  • Effects push me to think about thinking in separate components. This can be done without effects, sure, but having to sit there to think of the constructors for my effect really helps me
  • Effects help me hide implementation details. They allow me to write functions that take few arguments, are easy to use and hide the core complexity behind the interpreter of the effect
  • Effects are a dream to refactor:
    • Every function that uses an effect has it in its signature. Brilliant, I’m a single search away from knowing which parts of my code are talking to the database
    • It’s really easy to split one effect up into multiple, to combine multiple effects into a single one, and play around with structure in other ways. This is really nice
  • Type signatures tell me what how a function (indirectly) talks to the outside world. That allows me to ask questions “Hey should this function really be talking to the database or should that be the responsibility of some separate function?”. Generally, if your function depends on too many effects, you should start asking yourself these questions.

Generally, all of the above things can be done without effects. The trick is, effect systems push towards doing well by default. Of course, one can resist the push and still write terrible code using effects systems, but the push towards the right thing, and good defaults are a really good way to get a nice piece of code off the ground.

8 Likes

This is something I want to respond to on a different level: I interpret this comment as particularly harsh. This discredits effects systems as having a huge cost to solve problems that don’t exist. Besides this being something I vehemently disagree with, I don’t like the implication that I’m wrong for using it for a simple personal backup application. An application that’s clearly not sensitive.

I’m perfectly fine with discussing the pros and cons of effects systems. I’m perfectly fine with you not seeing the benefit, that you think other ways of designing software might be preferable. But don’t tell me that it’s software that solves problems that don’t really exist. That’s not a productive statement.

1 Like

Well maybe a personnal backup application is a good example of application where the “business” logic is to talk with the outside world and therefore the use of effect is perfectly valid.

Anyway, it was just an advice to @hellwolf, I am not telling anybody what to do.

Fair enough. I appreciate your alternative viewpoints, as they challenge me to think about what the real benefits of effect systems are.

That’s the bit I don’t like. I remind me of OOP, where encapsulation or black boxing is a core principle. It has its benefit but also draw backs which I prefer to avoid. Hidding is great until you need to actually dig into it. By definition the better it is hidden, the harder it is too find …

I think drawing a parallel to encapsulation is a good one. One key benefit Effect systems have over OOP is the stronger types. Subtyping can really make things harder to find in OOP. With effect systems, you can always go-to-definition to the precise effect. In a sense, it’s “hidden”, but only still always only one editor command away.

Combined with the point that effect systems are particularly easy to refactor, digging into it and throwing things around make it a much nicer experience than OOP.

Haskell is a dream to refactor, with or without effects …

Very true, but my point is relative to non-effectful Haskell code :slight_smile:. What I mean in particular is that refactors like “this function should not be doing X” are easy with effects, because you remove X :> es from the type signature, and the type checker will point you straight to all the places where your code (indirectly!) uses X.

This is opposed to using straight IO code in these cases, where there’s no type system guiding you into removing functionality from a function. For all you know, there’s a three-level-nested function that still sneakily does X.

Once again, one can achieve this same result in other ways in Haskell. My main point is that effect systems are one of the good ways of doing it.

I know it as well, because my code is pure or it is irrelevant. This is my main concern about effects, it open the door to impure function.

I’m not sure what “my code is pure or it is irrelevant” means. How is impure code irrelevant? Is that your particular use case or a more general point?

Opening the door to impure functions is a good question: do effect systems push programmers to having most of their logic intertwined with code that has side effects? My honest answer to that is I don’t know. Ideally, effect systems still mostly deal with the side effects. There are some pure effects (e.g. reader or state), but to me, the real power is in the side effect business.

I think for pure code, it would probably be wise to either use only pure effects, or no effects at all. Perhaps an example of this would be one of my other projects, glualint: its library is pure, it has no effects. That’s where all the parsing, pretty printing and linting logic is. The executable is where all the effects live. That’s the command line interface, the file reading, thread management, all that stuff. That’s where effects shine.

4 Likes

Extensible effects are just a nice way of organizing code. Every other benefit is secondary. It’s a design pattern that is nicely codified in modern Haskell.

For instance, I do 0 swapping of effects in my game “engine.” But the effect interface allows for “a la carte” capabilities for any consumer. And the fact that interpreter types are standardized (unlike mtl) means I can abstract over them! I have a way of building up arbitrary interpreters in a way where each one can do initialization and cleanup, and they can even depend on each other, enforcing initialization order. I call them Expansions. For instance, the sound & graphics Expansions require (by way of their type signatures) that SDL2 be initialized first.

So at the end of the day, the arguments of “effects are overkill” or “you could’ve done it some other way” fall short because they miss the point. I like how effects look and feel in-the-large. They’re my favorite way of doing architecture. And they don’t have any complexity cost to me - in fact, a lot of the suggested alternatives would have more complexity to me due to unfamiliarity. It’s free code quality!

9 Likes

We use MTL at work and I’ve never really tried effect systems before.
Effectful looks interesting, although I skimmed through the documentation and it seems like you need to enable quite a few advanced GHC extensions before you can get started:

  • GADTs
  • TypeOperators
  • TypeFamilies
  • DataKinds

I’m not sure how practical it would be in an environment where I might have to train junior developers, or even experienced programmers who have never used Haskell before.

1 Like

If number of extensions are to be considered, MTL does have a bunch of them. From Control.Monad.State.Class

{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
– Search for UndecidableInstances to see why this is needed
{-# LANGUAGE UndecidableInstances #-}

and you need these same extensions if you are gonna create an instance for an ADT. In the case you rely on GeneralizedNewtypeDeriving or DerivingVia, well, those are extensions too!

1 Like

That’s a very good question. In my experience, I’ve been able to get very far by learning, and then teaching things in the following order:

  • The core idea behind effects (dynamic dispatch, benefits in architecture, also testing)
  • Effectful’s documentation: Just reading through the readme, and then the haddocks of the modules
  • Playing around with simple effects, using copy/paste/edit from examples
  • Hit a case that requires unlifting, and deep dive into that world

The language extensions usually get copied along in the copy/paste/edit phase. That or the compiler helpfully says “you can’t do this without enabling this extension!”. Usually people then go “okay” before letting HLS add the language extension to the top of the file.

Those language extensions need to be learned eventually of course, but the nice thing is that people can play with it first. The explanation then becomes much easier, because in explaining you can point at their own code to show how the language extension makes things possible.

The most complicated part of effectful in my experience is (un) lifting. There are many such functions for different needs. To use them well, you need to understand some internals (specifically the environment and sequential vs concurrent effects) . I took some time to understand them, and I now do, but I feel like it is the most difficult barrier the people I work with hit when getting into effectful.

3 Likes

We have had Effectful adopted at work by people who are regular backend developers, and the general UX of the library makes it very forgiving to more junior developers. Most of the extensions you mentioned are justified to write list and express membership at the type-level, whereas GADTs are only necessary when one writes their own effect (which, presumably, one wouldn’t leave to the juniors).

I guess the strong mental barrier to break is “We can do things at the level of types, including having lists of types”, and once you get through it, Effectful is only one of the many things that are waiting to be studied and used.

9 Likes

I’d even argue writing extensible effects is often simpler than writing a new mtl-style class.

3 Likes