Interesting! I think I’m finding this code hard to imagine. I can certainly imagine a logger effect being used in handling another effect, but I can’t imagine it being used to create another effect. I guess at this point I’m out of suggestions unless you can link to some real code.
Sorry that I cannot specify concrete code now.
I could roughly talk why I need logging for creating handles though.
For instance, I find it good practice to log when connecting to a DB. That way, I can know if error happened later, and what was the problem.
I am making a desktop application for linux, and interfacing with DBus.
Connecting to DBus could be complicated, so I also put some logging inside it.
Oh yes, makes perfect sense. I guess I am just too used to seeing handlers in CPS form, such as
withC :: A -> B -> (C -> r) -> r
So I rescind my previous puzzlement.
Thank you, I should stop caring about having too many parameters and embrace the length of types.
With some global variables sprinkled around, it should be manageable I think.
createC :: A → B → IO C
I find it good practice to log when connecting to a DB
fwiw, it tried to tackle this requirement with the “build a fixpoint” approach. I did not find a direct way. The closest I managed to do (gist) was to return “initializer actions” along with each component, accumulate them, and execute them before application start. But it complicates the component somewhat, and allows components to exist in an unitialized state.
Perhaps libraries like registry
could handle this is a more natural way.
I am new to these effect handling libraries, pardon my ignorance. I see one of the motivations for effectful is the behaviour of StateT
in the event of an exception. I am referring to this section of the effectful README:
The problem is that state updates tracked by
StateT
within a computation wrapped incatch
are discarded when an exception is raised. This is confusing and will lead to bugs if one doesn’t know about this subtle behavior.
Isn’t this just a matter of working together to ‘correct’ this behaviour in StateT
code? If indeed there is some consensus it actually agree it requires fixing I suppose.
Around the section below the quote above, regarding the WriterT space leaks. If people are adopting CPS.WriterT
, then WriterT
being broken is not so relevant anymore is it not? At least not relevant as a motivator for the effectful library.
For references, here are two very helpful documents from the Effectful repository by Andrzej that each outline reasons why the status quo must change:
I was responding to some of the content in your first link.
No, it can’t be corrected in StateT
, by the nature of StateT
itself. StateT s m
by definition keeps track of state by returning it in the result of each action in m
. If some action in m
has no result (such as throwIO :: IO a
, or throwError :: ErrorT e m a
) then that state is lost forever. There’s no way to get it back.
So, if you want to be able to reference the state after such an action the state needs to be stored in a reference that is accessible regardless of the action in m
, such as an IORef
, STRef
, or implicitly such as in the way that effectful
handles state.
You could also just keep State
on the “outside” of your other effects:
ghci> runState (runExceptT (lift (put False) *> throwE "error")) True
(Left "error",False)
In fact, handling state through IO is very similar in this regard, because IO
also always needs to be on the “outside”.
Sure, but that doesn’t work for exceptions in IO
, which must be the base monad (it doesn’t have a transformer form) or pure exceptions (error
, undefined
, throw
) run in any base monad.
(Terminological question: don’t we usually use the terminology OuterT InnerM a
, so in this case you are actually suggesting keeping state inside the other effects?)
I do not understand the rationale for StateT with ExceptT. Ofc we should not use them with IO. Yet I do not see the problem in pure transformer stack, indeed order affects semantics! That is the whole point.
So if you don’t want to use IO
, nor recover from exceptions thrown from pure code (error
, undefined
, throw
), then you can use MTL/transformers, and the benefit you get in return is that you can combine them in different orders to (sometimes) get different semantics. That much I agree with.
Why is it the whole point? A property “rearranging the order doesn’t change behavior” is very nice to have, not to mention that it decouples the notion of “I want the shape of returned value to look like this” from “I want the code to behave like this”. There’s no reason to expect these should be related.
My opinion is that StateT
/ExceptT
situation is an accidental misfeature stemming from their definitions (similar to StateT
losing state updates on exceptions, which some people try to defend talking about “transactional behavior”) that bites you sooner or later. YMMV though.
Thanks. Could this not be more accurately described as a deficiency of the monadic type m
rather than a problem inherent in StateT
, in that as you allude to an error cannot be represented in m
and hence returned as part of a runStateT
?
Could this not be more accurately described as a deficiency of the monadic type
m
Perhaps, if you consider Either e
(as a Monad
) to be “deficient”. By design, Either e a
contains no a
s in the Left
case. That’s it’s whole point! And StateT s (Either e)
keeps track of its state by returning it as part of the a
. So when there is no a
(which is desirable) there is no state.
To cope with this you can either may Either e
and StateT s
cooperate, by arranging for StateT s
to smuggle its state into the Left
case, to be restored later (that’s the MonadBaseControl
way), or you can just embrace one monad to rule them all (that’s the ReaderT ... IO
and effectful
way).
The MonadBaseControl
way proved too complicated in practice, and it didn’t really work properly anyway. The ReaderT ... IO
way doesn’t allow you to remove effects and return to the pure world. The effectful
way seems to be the best of all worlds.
Hrm:
-
Bob wants the current state to be preserved across errors as much as possible in his program;
-
Jan wants transactional behaviour - errors discard the current state - in her library;
-
They both then try to use
effectful
.
…can effectful
“read minds”?
That doesn’t make sense. No effect system directly gives you “transactional state”, not even StateT s (Either e)
. In order to run it on a state you have to runStateT
the whole thing, at which point you get a s -> Either e (s, a)
and you’re no longer in a (stateful) effect system anyway.
Now, you could say that
transactionally :: s -> StateT (Either e) a -> StateT m (Either e a)
is an implementation of transactions in MTL. But then
transactionally :: s -> Eff (State s : Error e : es) a -> Eff (State s : es) (Either e a)
in an implementation of transactions in effectful
. That is, transactionality is not a property of the effect system, it’s just a combinator you write on top of your effect system.
Thanks for persevering with my queries. I’m glad it’s not beneath you to respond to relative newcomers like myself.
Well Either is deficient here isn’t it? In the sense that it does not carry the information we need at the points we need it. So I suppose modelling with a “richer” type might be a way to fix things in StateT. But personally I don’t like the MonadBaseControl way of doing things as you described it, sounds like a bit of a hack.