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

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.

I’m not sure about the “control over effects” bits.
It seems that there is (online at least) this sentiment that control over effects is the natural continuity of code purity, that if you think purity matters then control over effects should matter otherwise you are not consistent with yourshelf.

Well I disagree. I care (lots) about purity but I don’t over effects.
Purity matters because it guarantees that a function with the same inputs give the same output.
That is really helpfull for debugging but also to read code : you know that everything is under your eyes, there is no hidden state somewhere modifying things behind your back.
That’s why purity matters (for me), not to protect me for sending nuclear missile (I don’t have any), and I write mainly pure functions. The few IO I do are just an interface between the real world and the pure world, it’s not where the real business occurs neither the real bugs it just like a cable between a PC and it’s monitor. I need it but I’m not really interested in it.

Now I totally understand why people write effects libraries, it is an interesting experimental topic : how far can we go (or how far can Haskell let us go ?).
I understand why people want to use effects (for the same experimental reason, is it practical , does it make things easier).
I don’t understand why people are trying to sell it as the way to design software in production. I don’t think “dependencies injection” is valid reason neither. Modifying your code to be able to test it seems wrong.

No, it might give the impression but also encourage writting impure function under the effectful cover.

My advice would be don’t use effects until you actually need them (and you probably never will). Unless you working on a really sensitive application (which can send real nucleal missiles), it solves problems which don’t really exist (and so with a big cost).

For example one my production project is a backend for a website. The main effects are

  • logging
  • getting today’s date
  • read from the db
  • writting to the db
  • talking to accounting app (via http)

It seems the perfect project to use an effect library (after all, I already have identified a few effects), but

  1. which problem do I have which benefit from using a effect library ?
  2. How fine should the effects be ?

for 1), people will tell me that If a function needs today’s date, then I authorize them to write to the db.
Ok, but my handler actually just get the date and call a pure function whith the date as a parameter.
So the actuall core function can not access the db.
Moreover, if the function needed to access the db, then I will just change the effect list to allow access to the db.
2) Should writting a User or updating a Product be different effect ?
I guess writting to the DB and talking to the accounting app could be different effect.
Howewer, the talking to app is asynchronous (in case the server is down).
The way it works is accounting *actions" are actually saved in a queue (in the db).
And a thread poll the queue and execute the action (and retry if needed).
So in practice everything which can write to the db can launch a nuclear missile (because that’s what the accouting app does).

I’m sure a carefully designed effect systems could work fine, but I could equally come up with a bad one giving the illusion that everything is fine. And actually everything is fine, even without effects …

A problem I have in this app which I haven’t find a nice solution (which may or may not be related to effects) in permissions. The website is for wholesaler, so only user with the correct authorization can see the prices of the products. How can I guarantee that the prices don’t leak ? Could I have a ProductWithPrice and ProductWithoutPrice effect ?.
At the moment prices have the type Locker Price and extracting the price from a locker can only be done if an implicit parameter unlock is given. This implicit parameter can be set (or not) up stream.

2 Likes

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

Maybe. It might be relevant when the whole application is about making those effects less so when the effects are just a necessary evil.

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 …

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

I can see that, but again only if effect are part of the business logic.

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.

It’s only nice because you introduced effects in the first place

Still the same, is the business core to talk to the outside world or just an interface. In the first case I can see the benefit, but I don’t do that type of application (nor the Haskell beginner just doing toy project).

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.

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

What I’ve written is not clear indeed. What I mean is I try to do pure code with a “thin” layer of IO (I’m not the only one). What that mean is I know exactly what the pure code does in term of effects (nothing) and the thin layer is irrelevant because it’s just a wrapper around the main core functions. In a way the thin IO layer is just boilerplate. Of course this doesn’t work when the main purpose of the app is to pilot IO things (like backuping files ).

I have a look at your backup project and I can see how the pure code thin layer model doesn’t work there.
What I understand is that the effects (MountDrive, ExternalDiskBackup, etc …) correspond somehow to domain of responsibily. MountDrive has to do with mount, ExternalDiskBackup with external disc …
but there is control for example about write or readonly action. Having access to MountDrive gives function the right to check if a mount exists but also to unmount. That’s a perfectly valid design and I’m sure it works well in that case.

I guess if you were writing in OOP, you would have MountDrive , ExternalDiskBackup classes and singletons (in the OOP sense). All the code specific to mount will be in MountDrive.lua (instead of MountDrive.hs) and singleton instance could be swapped to allows mocking , dependency injections and so on.

My point is effects allows you to design this app in the exact same way as you would have done in lua with much strong guarantee but also more boiler plate …

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