I'm working on writing a Haskell wrapper for the GNU Lightning code generation library (http://www.gnu.org/software/lightning/), which in C consists of a series of macros that write to a certain global state in a pointer _jit, such as jit_add or jit_call. Now, I figure that can be wrapped fairly simply in a monad, which we'll call the Lightning monad:

-- syntax given here is just a sample.
incr = lightningEmit $ do
    prolog
    in <- arg
    getArg R0, in
    addi R0, R0, 1
    retr R0

However, I've come up with four possible designs for the Lightning monad. All but one would be reader monad transformers holding the pointer to the JIT state.

1) Lightning m a, where m is a MonadIO.
Pros: Users have the power of the monads they are using within.
Cons: Complexity, especially with having to handle monads with multiple returns like ListT.

2) Lightning a, which just wraps IO, and is a MonadIO.
Pros: Allows the use of other IO functions in the monad.
Cons: Allows the use of other IO functions in the monad.

3) Lightning a, which just wraps IO, and is not a MonadIO.
Pros: Simplicity; possibly permitting the code generation to be unwrapped with unsafePerformIO.
Cons: Referential integrity could still be violated.

4) Lightning a, which would be a free monad over the Lightning operations allowed.
Pros: Evaluation (in the IO monad) would be certain to be pure, which would permit or accommodate it being a monad transformer, or converted to a pure function on architectures where the Lightning library is not available.
Cons: Assembling, then disassembling to reassemble, a long list of instructions produces significant overhead, rendering it less suitable for JIT purposes. Interacting with the generated code is more difficult.

Are there any other options I am missing? Are there any possibly game-changing pros or cons I have failed to notice?

Thanks,

Paul Drees