Why use an effect system?

Off the top of my head: the side effect of calling C code that performs reads/writes gets muddled in with stuff like console output. It would be nice to know when code only performs memory reads/writes. But then we’d want FFI code that reads/writes memory to be embedded in “grand IO” sometimes.

That seems worth pursuing. I’m not sure these “mtl replacements” will pan out in terms of effort—lots of wrangling typeclass inference, which is much less studied than some other aspects of FP, and thus harder to justify/know it’s good.

inside Haskell, there’s another language; a halfway Prolog interpreter with lots of extensions which say few people know how to get amazing things done; I’m convinced in the end, this is not the way we want to do them. So I think this is going the wrong way; take things away from the current state of depenetly typed languages; this is not teachable or transferable. Libraries depend on these extensions; it’s not clear if it’s coherent or not.

It would be nice to know when code only performs memory reads/writes.

There would still be a role for ST s a, with it’s ability to be safely encapsulated using runST …unlike IO a.


[…] FFI code […]

(…yes, I did forget about the FFI examples; relevant posts updated.)


As for that quote by Doaitse Swiestra: it’s another validation of my “poor man’s Prolog” retort about (Glasgow) Haskell.

Since the discussion has died down I’d like to thank everyone who participated in this thread. I found it very interesting and learned a lot, which will help me develop Bluefin further. In particular I’d like to thank @sgraf for his summarizing and clarifying post and @vshabanov for challenging me to think of ways to make Bluefin easier to use with existing ecosystems, a challenge I will take to heart.

Just wanted to mention this now that I have access to my e-mail account again.

Effect systems are simply asking for more complicated types over various side effects, but typed effects are intrinsically unergonomic.

The best example is actually the IO a type, which, in comparison to Python, requires constantly throwing in operators (<$>, <&>, or fmap, <*>, >>=) to conduct “monad accounting” in order to make the types match.

This isn’t a complete downside. In return for having to do monad accounting, you are now more explicit in what you are doing and you are safer in terms of effect control. Your code is consequently more maintainable.

However, this further extends to more complicated effect systems; in a language with no effect system whatsoever, effects are ergonomic by simply using a function call. In default Haskell / handle pattern, you just use do notation or Functor / Applicative / Monad operators, which is a slight decrease.

With more complicated effect systems, however, it should be a goal to minimize the ergonomic cost of powerful effect systems, as the Handle IO advocates here have complained about. Type synonyms, type-level functions, etc, all the works to make sure that the type annotation for more powerful effect systems is both minimized and readable.

===

At the minimum, I’d suggest -XOverloadedMain, i.e, the ability to hide the effect system with a typeclass IsMain.

class IsMain a where
    toMain :: a -> IO ExitCode

instance IsMain (IO a) where
    toMain = fmap ExitSuccess

You could even implement instances for basic types;

instance IsMain String where
    toMain = fmap ExitSuccess . putStrLn

instance IsMain Int where
    toMain = fmap ExitSuccess . print

etc…

At the very least, we can achieve the dream of a 100% pure program; i.e, you have 0% explicit code in IO, with it all being handled by the typeclass.

Just run the hardcoded computation, get a print, then pipe it with shell operators.

If we’re sticking to monadic effects, i think a (!) pattern would be much better than another special typeclass, see, from idris: Interfaces — Idris2 0.0 documentation