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