
Reid Barton wrote:
I'll assume for simplicity and concreteness that ResultSet = [].
It more or less is. (But with a more complex internal structure, and correspondingly more complex (>>=) implementation.)
That's right. The type mismatches are telling you that there's a situation you haven't thought about, or at least, haven't told us how you want to handle. Suppose run s0 x = Right [s1a, s1b, s1c] and run s1a y = Left err, run s1b = Right [s2], run s1c = Left err'. What should the overall result of run_and s0 x y be? Somehow you have to choose whether it's a Left or a Right, and which error to report in the former case.
For the [] monad, there is a natural way to make this choice: it's encoded in the function sequence :: Monad m => [m a] -> m [a], where in this setting m = Either ErrorType.
Yeah, while testing I accidentally got a definition that typechecks only because I was using [] as a dummy standin for ResultSet. (Rather than the real implementation.) The sequence function appears to define the basic functionallity I'm after.
For your problem, it would probably be a good start to write an instance of Traversable for the ResultSet monad.
Wuh? What's Traversable?
In general, one way to make the composition of two monads m and n into a monad is to write a function n (m a) -> m (n a); this is the sequence method of a Traversable instance for n.
Oh, *that's* Traversable? Mind you, looking at Data.Traversable, it demands instances for something called "Foldable" first (plus Functor, which I already happen to have). (Looking up Foldable immediately meantions something called "Monoid"... I'm rapidly getting lost here.)
Then you can write join :: m (n (m (n a))) -> m (n a) as
m (n (m (n a))) --- fmap sequence ---> m (m (n (n a))) ------ join ---------> m (n (n a)) ------ join ---------> m (n a).
Um... OK. Ouch. :-S