[Haskell-cafe] An interesting monad: "Prompt"
Ryan Ingram
ryani.spam at gmail.com
Sat Dec 29 18:09:00 EST 2007
I posted the current version of this code at
http://ryani.freeshell.org/haskell/
On 12/28/07, Thomas Hartman <tphyahoo at gmail.com> wrote:
>
> Would you mind posting the code for Prompt used by
>
> import Prompt
>
> I tried using Prompt.lhs from your first post but it appears to be
> incompatible with the guessing game program when I got tired of
> reading the code and actually tried running it.
>
> best, thomas.
>
>
>
> 2007/12/4, Ryan Ingram <ryani.spam at gmail.com>:
> > Ask and ye shall receive. A simple guess-a-number game in MonadPrompt
> > follows.
> >
> > But before I get to that, I have some comments:
> >
> >
> > Serializing the state at arbitrary places is hard; the Prompt contains a
> > continuation function so unless you have a way to serialize closures it
> > seems like you lose. But if you have "safe points" during the execution
> at
> > which you know all relevant state is inside your "game state", you can
> save
> > there by serializing the state and providing a way to restart the
> > computation at those safe points.
> >
> > I haven't looked at MACID at all; what's that?
> >
> > > {-# LANGUAGE GADTs, RankNTypes #-}
> > > module Main where
> > > import Prompt
> > > import Control.Monad.State
> > > import System.Random (randomRIO)
> > > import System.IO
> > > import Control.Exception (assert)
> >
> > Minimalist "functional references" implementation.
> > In particular, for this example, we skip the really interesting thing:
> > composability.
> >
> > See http://luqui.org/blog/archives/2007/08/05/ for a real
> > implementation.
> >
> > > data FRef s a = FRef
> > > { frGet :: s -> a
> > > , frSet :: a -> s -> s
> > > }
> >
> > > fetch :: MonadState s m => FRef s a -> m a
> > > fetch ref = get >>= return . frGet ref
> >
> > > infix 1 =:
> > > infix 1 =<<:
> > > (=:) :: MonadState s m => FRef s a -> a -> m ()
> > > ref =: val = modify $ frSet ref val
> > > (=<<:) :: MonadState s m => FRef s a -> m a -> m ()
> > > ref =<<: act = act >>= modify . frSet ref
> > > update :: MonadState s m => FRef s a -> (a -> a) -> m ()
> > > update ref f = fetch ref >>= \a -> ref =: f a
> >
> > Interactions that a user can have with the game:
> >
> > > data GuessP a where
> > > GetNumber :: GuessP Int
> > > Guess :: GuessP Int
> > > Print :: String -> GuessP ()
> >
> > Game state.
> >
> > We could do this with a lot less state, but I'm trying to show what's
> > possible here. In fact, for this example it's probably easier to just
> > thread the state through the program directly, but bigger games want
> real
> > state, so I'm showing how to do that.
> >
> > > data GuessS = GuessS
> > > { gsNumGuesses_ :: Int
> > > , gsTargetNumber_ :: Int
> > > }
> >
> > > -- a real implementation wouldn't do it this way :)
> > > initialGameState :: GuessS
> > > initialGameState = GuessS undefined undefined
> >
> > > gsNumGuesses, gsTargetNumber :: FRef GuessS Int
> > > gsNumGuesses = FRef gsNumGuesses_ $ \a s -> s { gsNumGuesses_ =
> a }
> > > gsTargetNumber = FRef gsTargetNumber_ $ \a s -> s { gsTargetNumber_ =
> a }
> >
> > Game monad with some useful helper functions
> >
> > > type Game = StateT GuessS (Prompt GuessP)
> >
> > > gPrint :: String -> Game ()
> > > gPrint = prompt . Print
> >
> > > gPrintLn :: String -> Game ()
> > > gPrintLn s = gPrint (s ++ "\n")
> >
> > Implementation of the game:
> >
> > > gameLoop :: Game Int
> > > gameLoop = do
> > > update gsNumGuesses (+1)
> > > guessNum <- fetch gsNumGuesses
> > > gPrint ("Guess #" ++ show guessNum ++ ":")
> > > guess <- prompt Guess
> > > answer <- fetch gsTargetNumber
> > >
> > > if guess == answer
> > > then do
> > > gPrintLn "Right!"
> > > return guessNum
> > > else do
> > > gPrintLn $ concat
> > > [ "You guessed too "
> > > , if guess < answer then "low" else "high"
> > > , "! Try again."
> > > ]
> > > gameLoop
> >
> > > game :: Game ()
> > > game = do
> > > gsNumGuesses =: 0
> > > gsTargetNumber =<<: prompt GetNumber
> > > gPrintLn "I'm thinking of a number. Try to guess it!"
> > > numGuesses <- gameLoop
> > > gPrintLn ("It took you " ++ show numGuesses ++ " guesses!")
> >
> > Simple unwrapper for StateT that launches the game.
> >
> > > runGame :: Monad m => (forall a. GuessP a -> m a) -> m ()
> > > runGame f = runPromptM f (evalStateT game initialGameState)
> >
> > Here is the magic function for interacting with the player in
> IO. Exercise
> > for the reader: make this more robust.
> >
> > > gameIOPrompt :: GuessP a -> IO a
> > > gameIOPrompt GetNumber = randomRIO (1, 100)
> > > gameIOPrompt (Print s) = putStr s
> > > gameIOPrompt Guess = fmap read getLine
> >
> > 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.
> >
> > > gameIO :: IO ()
> > > gameIO = do
> > > hSetBuffering stdout NoBuffering
> > > runGame gameIOPrompt
> >
> > Here's a scripted version.
> >
> > > type GameScript = State [Int]
> > >
> > > scriptPrompt :: Int -> GuessP a -> GameScript a
> > > scriptPrompt n GetNumber = return n
> > > scriptPrompt _ (Print _) = return ()
> > > scriptPrompt _ Guess = do
> > > (x:xs) <- get -- fails if script runs out of answers
> > > put xs
> > > return x
> > >
> > > scriptTarget :: Int
> > > scriptTarget = 23
> > > scriptGuesses :: [Int]
> > > scriptGuesses = [50, 25, 12, 19, 22, 24, 23]
> >
> > gameScript is True if the game ran to completion successfully, and False
> or
> > bottom otherwise.
> > Try adding or removing numbers from scriptGuesses above and re-running
> the
> > program.
> >
> > > gameScript :: Bool
> > > gameScript = null $ execState (runGame (scriptPrompt scriptTarget))
> > scriptGuesses
> >
> > > main = do
> > > assert gameScript $ return ()
> > > gameIO
> >
> > _______________________________________________
> > Haskell-Cafe mailing list
> > Haskell-Cafe at haskell.org
> > http://www.haskell.org/mailman/listinfo/haskell-cafe
> >
> >
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/haskell-cafe/attachments/20071229/4f5a2464/attachment.htm
More information about the Haskell-Cafe
mailing list