[Haskell-cafe] Re: MonadPrompt + Gtk2Hs = ?
apfelmus
apfelmus at quantentunnel.de
Sun Jan 13 15:49:49 EST 2008
Felipe Lessa wrote: (abridged)
> Ryan Ingram enlightened us with MonadPrompt as a very nice abstraction for
> turn-based games, allowing easy programming and testing.
>
> I wonder how nicely it fits on a Gtk2Hs application. =)
>
> The problem lies in the fact that Gtk is event-driven, so
> every time we ask the user for something, we have to wait for the
> corresponding event that will bring us his answer.
>
> The standard way of solving the problem of running two sequential things at
> once is using threads
> [...]This not only feels hackish, but also doesn't scale very
> well.
> [...]This feels hackish and fragile as well, specially
> because it is difficult to hunt down bugs.
>
>
> But Prompt has a very *very* nice property we're missing to take advantage of
> here: it's is *pure*.
>
> "If you wanted to add undo, all you have to do is save off the current Prompt
> in the middle of runPromptM; you can return to the old state at any time."
>
> Eventually this feature rang some bells: you can save not only when you
> want to undo, but also when you want to ask something to the user.
>
> let's see how:
>
>> lastAttempt :: AttemptCode
>> lastAttempt showInfo entry button = do
>> game <- guessGameNew
>> runPromptM' game
>> where
>> -- Signature required
>> runPromptM' :: Prompt GuessP Int -> IO () -- note: not (IO Int)!
>> runPromptM' (Prompt (Print s) c) = showInfo s >> runPromptM' (c ())
>> runPromptM' (Prompt Guess c) = do
>> mfix $ \cid -> do
>> let cont guess = do {signalDisconnect cid;
>> runPromptM' (c $ read guess)}
>> onClicked button $ entryGetText entry >>= cont
>> return ()
>> runPromptM' (PromptDone attempts) = do
>> showInfo $ "You took " ++ show attempts ++ " attempts."
>
> No threads, no MVars, no nothing.
> No freezes, no races, no exceptions, no overheads.
Marvelous work! Here's a short summary:
The general idea was to implement a game in a modular way, namely such
that the game and interaction logic is written in a separate monad which
can be "plugged" into different interfaces like command line, undo, GUI
etc. . MonadPrompt (aka the free monad) lets you do exactly that.
Command line is straightforward, Ryan showed how to do undo and Felipe's
post shows how to do it in a GUI framework which are notorious for being
event-based and thus a tricky target for this particular task.
The insight is that the actions from the MonadPrompt like
Prompt Guess c or Prompt (Print s) c
*are* the game state. What to do in the state
Prompt Guess c -- some function c
? Well, we wait for the user to press a button, and then we feed the
text he entered in some text field to c which advances the game to a
new state. More precisely, we register a button event for that and
return control to the GUI framework for the waiting. What to do with a
monadic value
Prompt (Print s) c
? Well, we print the message and immediately proceed with c , i.e.
without waiting for user input this time. In the end, the only unusual
thing about our game state is that it already know how to continue,
namely via the continuation c .
Felipe explored another option, namely to run the game monad in a thread
that communicates with a separate GUI thread. Conceptually, this is
closer to the feeling of the game monad, namely that it "runs through".
But getting the communication between threads right is a nightmare.
And in a sense, /threads are exactly the abstraction we wanted to
implement in the first place/, only with more problems. How are threads
themselves implemented? Well, in essence, the scheduler which runs
threads just sees states like
Prompt ReadMVar c -- this thread waits for a message
Prompt (PutMVar x) c -- dispatch a message and proceed
and evolves them. Of course, ghc's threads are closer to the machine and
hence more fine-grained than that (pure computations can be suspended)
with corresponding advantages (more interleaving, good for staying
responsive while doing work) and drawbacks (loss of atomicity). The
reader may want to try to implement his own (toy-) thread library with
Prompt . Solution here:
K. Claessen. Poor man's concurrency monad.
http://www.cs.chalmers.se/~koen/pubs/jfp99-monad.ps
For less "toy", see also
P. Li, S. Zdancewic. Combining events and threads for scalable
network services.
http://www.seas.upenn.edu/~lipeng/homepage/papers/lz07pldi.pdf
Maybe the property that distinguishes the harmless game monad from the
dreaded threads is: they have forks. :) I mean, threads run in parallel
and can/need to communicate and spawn whereas the game monad runs as one
and is unable to soliloquize.
> Eventually this feature rang some bells: you can save not only when you
> want to undo, but also when you want to ask something to the user.
> Unfortunately, I still haven't come up with a nice higher order function
> that generalizes this work without reinventing Prompt on an isomorphic type.
Oh, what kind of generalization do you have in mind?
Regards,
apfelmus
More information about the Haskell-Cafe
mailing list