Search code examples
haskellfunctional-programmingmonadsstate-monad

The Monad Challenges - A Missed Generalization


I'm going through The Monad Challenges.

At the section A Missed Generalization, I'm supposed to have at least this code (I've removed parts not relevant to the question), where Gen looks a lot like the State Monad,

-- Imports and other stuff that hide Prelude
-- For instance, the do notation is NOT available at this stage of the challenges

type Gen a = Seed -> (a,Seed)

genTwo :: Gen a -> (a -> Gen b) -> Gen b
genTwo g f s = let (a,s') = g s
               in f a s'

mkGen :: a -> Gen a
mkGen a s = (a,s)

generalB :: (a -> b -> c) -> Gen a -> Gen b -> Gen c
-- I've implemented it as follows and it works
generalB f a b s = let (x,s') = a s
                       (y,s'') = b s'
                   in (f x y,s'')

The text of the "assignment" reads

[…] you might not have implemented generalB in terms of genTwo. Go back and look at your generalB implementation and if you didn’t write it in terms of genTwo, do that now and call it generalB2. Doing this should get rid of the state threading between generators.

Is unclear to me what the solution to this should be, especially in view of the fact that the paragraph above doesn't mention mkGen. Assuming I'm able to apply f to the inside of a and b, I would still get something of type c, which I have to shove in a Gen, and I don't see how I can do that without mkGen or, alternatively, without using (,) explicitly (as I did in the above implementation).

Even assuming that the text implies that I should use mkGen, how should I go about it to get rid of the state threading?

With some editing I was able to come up with this

generalB2' f a b = genTwo a (genTwo b . (mkGen .) . f)

but I hardly believe this is the intended solution, because it's far from being readable, in my opinion. Also, getting to this form was a bit harder than anything else so far in the challenges, but it was after all just mechanical, so it didn't really pose a difficulty from the point of view of understanding monads, I believe, so I really think I took a wrong turn here, and I'd like some help.

I wonder whether the authors of the challenges hang out here on StackOverflow.


Solution

  • Your solution is probably close to the intended solution, although you might be able to make it more readable by eta-expanding it. You might even consider writing it using do notation, but still use genTwo and mkGen.

    As far as I can tell, mkGen is a 'disguised' return function, and genTwo likewise is a 'disguised' monadic bind (i.e. >>=).

    The type of generalB (and generalB2) is equivalent to liftM2, which is implemented like this:

    liftM2  :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
    liftM2 f m1 m2 = do { x1 <- m1; x2 <- m2; return (f x1 x2) }
    

    That is, in terms of return and >>= (which you don't see, because it's using do syntax).