
I have some difficulties to see the use of PromptT, because in the tutorial,
this type is never mentioned, and its operations (Return and :>>=) are
instead constructors of ProgramT...
Would you have some concrete examples? Because there I'm a bit lost (since
the tutorial doesn't match the operational package as it is, because of the
type PromptT)...
2010/4/14 Heinrich Apfelmus
Bertram Felgenhauer wrote:
Yves Parès wrote:
I answered my own question by reading this monad-prompt example: http://paste.lisp.org/display/53766
But one issue remains: those examples show how to make play EITHER a
human
or an AI. I don't see how to make a human player and an AI play SEQUENTIALLY (to a TicTacToe, for instance).
A useful idea is to turn the construction upside-down - rather than implementing the game logic using MonadPrompt (or operational), implement the players in such a monad.
A sketch:
{-# LANGUAGE GADTs, EmptyDataDecls #-} import Control.Monad.Prompt hiding (Lift)
data Game -- game state data Move -- move
data Request m a where Board :: Request m Game MakeMove :: Move -> Request m () Lift :: m a -> Request m a
type Player m a = Prompt (Request m) a
Just a small simplification: it is not necessary to implement the Lift constructor by hand, the operational library implements a generic monad transformer. The following will do:
import Control.Monad.Operational
data Request a where Board :: Request Game MakeMove :: Move -> Request ()
type Player m a = ProgramT Request m a
game :: Monad m => Player m () -> Player m () -> m () game p1 p2 = do g <- initGame eval' g p1 p2 where eval' g p1 p2 = viewT p1 >>= \p1' -> eval g p1' p2
eval :: Monad m => Game -> -> Prompt Request m () -> Player m () -> m () eval g (Return _) _ = return () eval g (Board :>>= p1) p2 = eval' g (p1 g) p2 eval g (MakeMove mv :>>= p1) p2 = makeMove mv g >>= \g -> eval' g p2 (p1 ())
This way, you are guaranteed not to break the lifting laws, too.
What have we achieved? Both players still can only access functions from whatever monad m turns out to be. But now each strategy can pile its own custom monad stack on the Player m monad! And of course, the use of the m Monad is completely optional.
Of course, the custom monad stack has to provide a projection back to the Player m a type
runMyStackT :: MyStackT (Player m) a -> Player m a
Fortunately, you can't expect anything better anyway! After all, if the game function were to accept say LogicT (Player m) as well, this would mean that the player or AI could interleave the game arbitrarily, clearly not a good idea.
Mapping between various 'm' monads may also be useful:
mapPlayerM :: forall m1 m2 a . (forall a . m1 a -> m2 a) -> Player m1 a -> Player m2 a mapPlayerM m1m2 pl = runPromptC return handle pl where handle :: Request m1 x -> (x -> Player m2 a) -> Player m2 a handle (Lift a) x = prompt (Lift (m1m2 a)) >>= x handle (MakeMove mv) x = prompt (MakeMove mv) >>= x handle (Board) x = prompt (Board) >>= x
This could be used to lock out the AI player from using IO, say.
Shouldn't this actually be a member of the MonadTrans class?
mapMonad :: (Monad m1, Monad m2, MonadTrans t) => (forall a . m1 a -> m2 a) -> t m1 a -> t m2 a
?
Regards, Heinrich Apfelmus
-- http://apfelmus.nfshost.com
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe