Why use an effect system?

Yes, this is exactly the case currently.

I wouldn’t explain it to beginners. It’s an abstract type. Its internals are irrelevant to most people, including beginners. I’m not an educator, so maybe I’m missing something, but I don’t understand the need some people feel to “explain” IO (for example, by saying it’s a “pure program which produces an impure program for the RTS to execute”).

3 Likes

If the intent is for multiple libraries to share Eff on the type level, what would the term-level definition for Eff be? Also I don’t know how you’d prove All is the same as “all effects in scope” with current Haskell type-level features.

I can definitely imagine a future version of GHC that ships an effect system as a boot library plus an extension that switches between presenting all effects through Eff or through IO (for beginners), but that’s about it.

…so presumably that problem has already been solved.

Yes, that is my claim: IO-based effect systems such as Bluefin and effectful solve the “Haskell effect systems” problem.

2 Likes

No: IO a-based effect systems such as Bluefin and effectful can solve the “Haskell effect systems” problem once they are independent of IO a, to the point of being able to use Eff [...] a (with “external” effects) in FFI calls.

So is this the case now, or isn’t it?

A function without any effect is called total and corresponds to mathematically total functions – a good place to be. Then we have effects for partial functions that can raise exceptions (exn), and potentially non-terminating functions as div (divergent). The combination of exn and div is called pure as that corresponds to Haskell’s notion of purity. On top of that we find mutability (as st) up to full non-deterministic side effects in io.

The Koka Programming Language

1 Like

So can those full non-deterministic side effects in io be encoded in eff, to the point of being able to define:

main :: Eff All ()

instead of:

main :: IO ()

…in Haskell?

No idea, that’s above my level.

1 Like

Well, both questions were intended for @tomjaguarpaw’s attention - but thank you anyway.

About mocking with records-of-functions:

Passing records of functions makes you responsible for juggling all that as extra arguments instead of constraints. It’ll definitely work, the question is just how much inline visual clutter the team you’re working in is willing to endure.

In modern OOP programming (which the record-of-functions approach closely resembles) most of the administrative tedium of passing parameters for component wiring is handled by type-directed dependency injection frameworks, which also exist in Haskell.

One might object that having to learn the workings of a DI library is as much of a burden as having to learn the workings of an effect system. But one key difference I think is that (good) DI libraries are not intrusive, they don’t require the “wired” components to know about the library. And they are only used in isolated points of your application, close to the top level.

But I suspect even DI frameworks might be overkill for simpler applications and you can get by by wiring things manually at the beginning of your application.

4 Likes

I don’t think the “why” has been answered in this thread, the subject seems to be very much a matter of opinion.

Are there good learning resources to learn about these techniques in a gradual fashion?

I can understand why imposing a constraint on the effect at the type level could give some nice garantees: security (there’s no network access in this part of the code), readability (the db-related bug can’t be in this part of the code), etc. But I also feel sympathetic to the view of keeping things simple.

If I understand things correctly, there are roughly five ways to organize one’s app (oldest/easy to newest/difficult)

  1. handle pattern
  2. mtl
  3. transformers
  4. free monad
  5. effect system

I own a few books (which I still have to read) and they pretty much talk about transformers and that’s it.

I understand effect systems are newer, but I’d be nice to have an author spell out and demonstrate the trade-offs of each of these techniques.

6 Likes

The more pertinent question would be:

Why use an effect system instead of IO a?

especially for the ones that are based on IO a (like bluefin and effectful) - to claim that an effect system is superior to IO a when the effect system relegates “external” effects to IO a (by ultimately returning an I/O action) is contradictory at best.

Hence my challenge to define IO a as a special case of a more general effect-system type:

type IO a = Eff All a  -- what is "Any"?

main :: Eff All ()  {- foreign export ... "hs_rts_Main_main" main :: Eff All () -}

foreign ... actionWithEffect :: ... -> Eff All ()

which neither bluefin or effectful have so far proven capable of, despite what has been claimed here.

But if you’re still interested, Kleidukos’s presentation provides an overview of the technique.


But the author of which effect system?

Browse and search packages | Hackage (effect system (deprecated:false))

…each system is different in some way, so any specific insights you may have from seeing examples in one effect system may not be applicable elsewhere: right now, there seems to be no “grand unifying formalism” for effect systems - their only point of agreement is their mutual disdain for the monadic-transformer “layer-cake” !

I don’t get your argument: true, IO and Eff from effectful have equal expressive power, but how does it prevent one of them to be superior to another (universally or in given circumstances)? All Turing-complete programming languages are equally expressive, and yet some are more suitable than others.

This is a rather pointless challenge, because authors of effect systems do not intend to replace the definition of IO in Haskell (be it GHC implementation or Haskell Report).

7 Likes

I don’t get your argument: […]

Imagine downloading a compiler only to discover that the programs it produced had to be started manually in assembly code


This is a rather pointless challenge […]

Think of it as the “external”-effect version of this classic:

Sorry, @atravers, I genuinely appreciate your erudition, but I’m afraid I don’t have necessary time to unpack and reconstruct your arguments from multitude of links and quotes. I mean no disrespect, but I’d really appreciate if your posts were more self-contained.

So far it seems that your argument is of the same sort as “To claim that a functional language is superior to assembly when it ultimately relegates all effects to it (by executing on a von Neumann architecture) is contradictory at best”. Am I wrong?

14 Likes

Yes.

  • Why use a programming language? To avoid programming in assembly languages.

  • Why use an effect system? To avoid programming in IO a.

But “external” effects breaks this layering of abstraction - an effect system must ultimately send its users back to IO a to deal with those effects.

Well, yes, it’s likely that a complex application using an effect system would have to convert back and forth between Eff and IO to communicate to non-Eff libraries and to emit main :: IO () in the end. To continue our analogy, this is no worse than a C application, which employs inline assembly and eventually is compiled to assembly code again, which is still (in certain circumstances) better than writing assembly all the way through.

4 Likes

Sorry, I wasn’t very clear. I meant any effect system, it doesn’t matter which one specifically. Even though yes they differ in the details, it seems to me that they have a common concept in general.

I should point out to any curious reader that I was very happy to stumble upon this series of blog posts (google translated here) a while ago, that covers the topic at a superficial level but gives a good overview (the series in question is “Code architectures in Haskell (1 to 6)” btw).

There are accompanying youtube videos as well but it’ll be less pleasant for you if you don’t speak french :slight_smile:

So, what I’m saying is that I’d be glad to read a “deep dive” on the topic, not from a theoretical but from a practical point of view (building a small to large system).

Personally, I got really interested in Haskell when I saw what one could do with free monads. I could imagine the technique being great for writing certain small programs where performance isn’t a concern. But I haven’t studied the subject properly yet to really make up my mind.

1 Like

I just don’t get it. Those are Haskell effect systems, and Haskell relies on IO. How is this a practical problem?

Maybe you can show me on this example, where it arises as a problem. Let’s imagine some standard web API.

addCar :: Car -> IO Int
getCar :: Int -> IO (Maybe Car)
version :: IO Version

All those functions can, in theory, execute any IO function, but they probably don’t do anything creative. But still, let’s get fancy.

addCar :: (WriteDB :> es) => Car -> Eff es Int
getCar :: (ReadDB :> es) => Int -> Eff es (Maybe Car)
version :: Eff es Version

Are there any upgrades here? No, not much. Of course, we were using a database and not much else, and of course getCar doesn’t write to it! And version is pretty much pure.

Now imagine the problem scales in space and time. We have many more endpoints (hundreds, maybe) and maybe we didn’t touch the codebase for a year.

Then there is a bug, some invalid state in our database. Where do we look for it? For sure, not in ReadDB endpoints, they don’t change the state of our database! We eventually find a bug, maybe a little bit faster, thanks to that insight.

The next task is to add some tests, to make bugs disappear. We could be smart and add tests only for WriteDB endpoints, as ReadDB don’t really do anything. But metrics are a thing in IT and generally triumph reason, so let’s test everything.

Now the tests are slow, they run against a real database. We could speed them up by running some in parallel, but for which tests is it safe? Of course, for ReadDB ones!


Above is, of course, what we imagine effect systems could do for us. Reality will be more complex and troublesome. But it illustrates that whether, if we, “in the end”, program against IO, it doesn’t even matter.

I hope it is a better example to debate “Why use an effect system?”

6 Likes

Now why was it so simple to find the bug in that example? Because you only had one effect system to deal with.

So expand your example to a large application that has to use multiple effect systems (in libraries) that only interact with each other via IO a, and imagine trying to find a bug in that labyrinth!

1 Like