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

@eddiemundo Can you elaborate? I don’t understand this sentence :thinking: I thought effectful worked fine with streamly as you should be able to just put e.g. SerialT on top of Eff es.

2 Likes

I don’t use effect system and I’m not planning too. I’m happy with yesod like Handler.

2 Likes

In my experience, existing code and libraries are the biggest barrier to going full effectful. I’ve been rewriting some code to effectful, and found that free monad code is the easiest to rewrite.

I haven’t tried rewriting MTL heavy code, but I got the feeling that it would be much more effort. I may be wrong, it may be that particular project that I was eyeballing for a rewrite, but I got the feeling that an effectful architecture would look way different than an MTL architecture.

In the software that has been rewritten to effectful, MTL still remains in use in libraries that are coupled to it. Persistent for example demands a ReaderT when executing queries, and conduits have the ConduitT and ResourceT.

What I then really like about effectful, though, is that you can often limit those MTLs to some effect, in such a way that it doesn’t feel awkward that both are present in the code base. When the transformer is a class, effectful even has a fancy guide for it.

With Persistent you can just make an effect constructor that runs a transaction. That transaction will be wrapped in that ReaderT, because all query functions demand it, but that’s only in the type signature. For all the caller cares, they’re still in Eff.

Here’s an example with sqlite, which demands not only a ReaderT, but also a logging and resource:

Here’s the call site, which has no mentions of any transformers:

2 Likes

The real question is: if you use effect systems, do you still use transformers/MTL somewhere in your code? If so, where?

In the code bases where I’ve introduced Effectful, no, there are no more 1st-party transformers/MTL classes. Thanks to Effectful’s compatibility with 3rd-party transformers, I can write effects for those quite easily.

2 Likes

Regarding IO, I think you do not usually need additional layers above that. It already provides states and exceptions, and you can pass environments as arguments.

IO provides states and exceptions, but also everything apart from these. Once you have IO/MonadIO in the stack, anything can happen. You forfeit any guarantees about what your program does.

One of the biggest selling points of Haskell is purity, and control over effects. If you have a function using constraints (MonadDatabase m, MonadLogging m) you know it doesn’t explicitly throw exceptions, or send HTTP requests. It can use some general database and logging, a specific implementation to be determined later. With all functions like a -> IO b, we are back to Python land.

1 Like

I think I meant what you said. I never really understood what under, or on top, inside, or outside meant. When I said inside I was thinking of Stream (Eff es) a being a series of eff computations producing as so the Eff es is “inside” the stream (or list). I don’t think I said that didn’t work.

On the other hand I feel like it’s not possible to have the other way, a streamly-like effect, or at least weird/hard.

1 Like

Just a thought:

Is effect system qualitatively more principled than IO monad?

If so, having both existing as a way to tackle RealWorld effects may be an analogue to unprincipled FP language versus Haskell purity approach.

Is it a crazy idea, some day, to tag library packages IO monad free in order to give principled effect approach some preferences?

1 Like

I like mtl-style for published libraries, and plain IO with a reader for apps.

10 Likes

That example of SqliteEffect.hs is great! I was feeling off that the documentation for effectful tells you to provide canonical orphan instances if the library you want to use uses MTL style type classes. But with your example I can fix a concrete monad stack and use that inside an interpreter, as most of those classes are to be run on some kind of stack with IO at the bottom. No instances have to be provided.

You are awesome!

1 Like

@arybczak’s set of benchmarks mentioned in another post is also worth looking at - note the entries for:

  • countdown.1000.reference (ST) - 6.55 microseconds

and:

  • countdown.1000.cleff (IORef).shallow - 25.6 microseconds

  • countdown.1000.cleff (IORef).deep - 25.9 microseconds

If IORefs are the only reason why the IO type is being used, I would be interested in seeing if there’s much of a difference for STRef-based versions of countdown.1000.cleff - if that difference is trivial, the choice of using a pure, ST, or IORef-based approach would seem to be a matter of personal preference e.g. one isn’t happy with using something that has an implementation but no denotation (at least in Haskell).

Having said that, if this thread is any measure, it isn’t so much a choice between being effect or IO-based, but whether it’s monadic or ordinary (Haskell) syntax: over one-quarter century after it officially arrived in Haskell (v. 1.3), more that a few still find the monadic interface irritating to deal with…

1 Like

I did a similar thing when I rewrote my game “engine” to use cleff, except for apecs. It’s a cool trick for interop with any transformer!

import Cleff
import Apecs qualified as Upstream

data ApecsE world :: Effect where
  LiftApecs :: Upstream.SystemT world m a -> ApecsE world m a

runApecs :: IOE :> es => world -> Eff (ApecsE world : es) a -> Eff es a
runApecs w = interpret $ \case
  LiftApecs st -> withToIO $ \toIO -> do
    toIO $ Upstream.runSystem st w

makeEffect ''ApecsE

I also wrote a wrapper around every apecs combinator (cmap etc) that used Eff instead of SystemT. I did need an inference helper due to the polymorphism but it works like a dream!

2 Likes

Is effect system qualitatively more principled than IO monad?

Yes, of course, because of

As far as I’m concerned the main point of effect tracking in Haskell is that you know from the types when there are not effects in a piece of code. If you use IO everywhere you can’t tell that! You can never remove/handle an "IO effect".

3 Likes

…apart from the obvious case where the code isn’t monadic to begin with.


…if the code still uses IO directly - another option is to use a newtype declaration as the basis for a new abstract monadic type, one that only allows certain IO effects to be used.

But this assumes that all the allowed effects are all I/O-centric, which e.g. isn’t the case for encapsulated state like ST, so a combination of those types is needed. But not all monadic types (and their effects) can be combined directly. It is because of that inability that Haskell now has monad transformers and effect systems.

I quickly realized that my musing was a moot, since we have already been doing effect systems without IO pollution. That is mtl for long time.

But mtl vs. effects seem a dividing point for the ecosystem at some point

1 Like

But division might not be a bad thing if we have enough energy in the ecosystem.

E.g. the Js/Ts ecosystem have multiple active front-end back-end frameworks at any given time.

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.