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.
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.
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.
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…
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!
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".
…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.
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.
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…
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
which problem do I have which benefit from using a effect library ?
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.
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.
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.
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.