Hacker News new | ask | show | jobs
by tome 695 days ago
I'm not convinced about the dismissal of option 2. I agree ST is clunky but not for the reasons given. It's clunky because it's impossible to mix with other effects. What if I want ST and exceptions, for example, and I want the presence of both to be tracked in the type signature? ST can't do that. But my effect system, Bluefin, can. In fact it can mix not only state references and exceptions, but arbitrary other effects such as streams and IO.

* https://hackage.haskell.org/package/bluefin-0.0.2.0/docs/Blu...

* https://hackage.haskell.org/package/bluefin-0.0.6.0/docs/Blu...

2 comments

Nice, first I'm hearing of bluefin - I'll be sure to check it out.

As an aside, I watched an Alexis King stream (which I can't now find) in which she did a deep dive into effect systems and said something along the lines of: algebraic effect systems should not change their behaviour depending on nesting order e.g. Either<State<>> vs State<Either<>>.

Does bluefin have a particular philosophy about how to approach this?

I agree with Alexis. Bluefin approaches this by not actually having a nesting order. The effects that can be performed as specified as function arguments, so they can be freely reordered without changing behaviour. effectful, which was one of the inspirations for Bluefin, is similar but uses constraints instead of function arguments, which are even more free to reorder.

> I'll be sure to check it out

Great! If you have any questions or thoughts then feel free to file an issue on the repo (https://github.com/tomjaguarpaw/bluefin/issues/new).

Sorry, I don’t know why my post on bots was removed. I couldn’t reply to you. Oh well.
Isn't mixing of effects exactly what monad transformers are for? AFAICT you want an `ExceptT e ST` for some exception type `e`.

https://hackage.haskell.org/package/mtl-2.3.1/docs/Control-M...

Oh, I meant it's impossible to mix ST with actual exceptions as implemented in the RTS, rather than with ExceptT which simulates exceptions in pure code (like StateT simulates mutable state in pure code).

You're right, through a stroke of luck it's possible to use `ExceptT e ST r` and either handle the exception part first, to get `ST r`, or handle the ST part first to get `Either e r`, so in that sense you can "mix" exceptions and ST. That doesn't work for all transformers though. If you have `Stream (Of a) ST r` then you must consume the stream first. You can't run the ST part an get a pure stream `Stream (Of a) Identity r`. So in that sense ST can't be mixed with other effects. Bluefin does allow you to do that, though.

Yes, but transformers have a few drawbacks: the order of stacking alters behaviour, and you need to write n^2 instances for n transformers.

Compare ExceptT e (StateT m a) and StateT (ExceptT e m a): if you just want your computation to have state and exceptions the difference shouldn’t matter.

> if you just want your computation to have state and exceptions the difference shouldn’t matter

But... you don't just want that. You almost certainly care whether state changes are discarded when an exception is thrown. I don't claim that the types there are the most obvious or natural way to specify that, but there is a meaningful difference that shouldn't be handwaved away.

The fact that with transformer stacks you can wind up with state changes being discarded on exception if you get the stack arranged in a particular order isn't what I'd call a _feature_ of transformer stacks :-)

If effect A is invoked before effect B, effect A should happen and stay that way: if I update state before an exception is thrown, the update should persist. If I send a network message before an exception is thrown, the network message is still sent. If I launch the nukes before an exception is thrown, the missiles are still flying.

If I want to batch up state updates to only happen together as a unit, I'll use a transaction effect.

I might have this wrong but I think if you want state and exceptions you probably want StateT (ExceptT e m a). The alternative would be to have state or exceptions, i.e. when you have an exception you no longer have state (which might be a legitimate type in some cases).
Remember that transformers are "inside-out", i.e. `StateT (ExceptT e m) a` is isomorphic to `m (Except e (State a))`. If we want to keep state if an exception occurs, you need a `m (State (Except e a))` which is `ExceptT e (StateT m) a`.
Yeah I could never keep this straight
The way I remembered it, before I internalized it, was to think about applying the run functions one at a time. runSomethingT will take a `SomethingT ... m ... a` and give you some kind of `m (... a)`.
Also their other well known problem: you lose the program state if an exception is thrown in the monad above.
Yeah, that's basically the same problem as `StateT s (ExceptT e m)` but for `StateT s m` where `m` throws exceptions.