
I was wondering why, since IO is an instance of MonadFix [1], and therefore of ArrowLoop (Kleisli m), and since "The loop operator expresses computations in which an output value is fed back as input, even though the computation occurs only once." [2], the MonadFix or ArrowLoop class (through use of mfix or loop, respectively) doesn't appear in anyone's suggestion, where the top-level state was the thing looped over.
oh, but it does!-) see 'proposal 2: top-level <-', and especially John Meacham's elaboration. 'mdo' is recursive do-notation, based on 'MonadFix', which for 'IO' is based on 'fixIO' (John's email gives references). (*) the problem with that is what happens to multiple bindings: according to the usual 'mdo'-translation, they are interpreted as a *sequence*, so order matters, which is kind of a big change for top-level bindings spread over a hierarchy of modules. as is the potential for allowing arbitrary IO actions to be performed as part of evaluating a set of recursive bindings and imports. see the wiki page for some of the issues and proposed workarounds. what is different about the variation i proposed is that the only thing that is merged into the evaluation of top-level bindings is the creation of some mutable variables, which are not even explicitly accessible, but are only used behind the scenes, to realise sharing. this is a kind of effect for which the ordering is immaterial, and since this effect does not depend on the actual IO action being shared, we do not need to know anything about that IO action either to guarantee that we can order bindings any way we like. and since the actual IO action being shared is not performed unsafely, it remains in the IO monad, and has to be invoked explicitly, so this variation should also be safer (no side-effects due to mere module import, for instance). it might still make sense to interpret '=<'-bindings via 'mdo', to allow for mutual recursion in the bindings. but since all top-level bindings are now either of the form 'var =< io', where 'io' will not be executed until 'var' is invoked within the 'IO' monad, or of the form 'let var = expr', where no 'IO' effects are involved, the ordering of the bindings does no longer matter. as i think it should be. hth, claus (*)
Or is this more or less what is going on in the function .. oneShot :: IO a -> ACIO (IO a) .. but without explicitly using the MonadFix or ArrowLoop classes?
oneShot, mkOnceIO, and fixIO, have an implementation technique in common, which is to allocate space for a result, then executing some code to figure out what that result might be. by passing the reference to where the result will be stored to the code computing it, cyclic representations of recursive structures can be constructed.