<div dir="ltr"><div style><font face="arial, helvetica, sans-serif">You can use type classes and polymorphism to get the restrictions you want.</font></div><font face="courier new, monospace"><div><font face="courier new, monospace"><br>

</font></div>class Monad m => ReadExp m where</font><div><font face="courier new, monospace">  readAccount :: m Int</font></div><div><font face="courier new, monospace">  onTimer :: m () -> m ()</font></div><div><font face="courier new, monospace"><br>

</font></div><div><font face="courier new, monospace">class ReadExp m => WriteExp m where</font></div><div><font face="courier new, monospace">  writeAccount :: Int -> m ()</font></div><div><font face="courier new, monospace">  setVistory :: Bool -> m ()</font></div>

<div><font face="courier new, monospace"><br></font></div><div><font face="courier new, monospace">instance ReadExp Exp ...</font></div><div><font face="courier new, monospace"><br></font></div><div><font face="courier new, monospace">instance WriteExp exp ...<br>

</font><div class="gmail_extra"><font face="courier new, monospace"><br></font></div><div class="gmail_extra" style><font face="courier new, monospace">-- works fine</font></div><div class="gmail_extra"><span style="font-size:13px"><font face="courier new, monospace">victoryRule :: ReadExp m => m ()</font></span></div>

<div class="gmail_extra"><span style="font-size:13px"><font face="courier new, monospace">...</font></span></div><div class="gmail_extra"><font face="courier new, monospace"><br></font></div><div class="gmail_extra" style>

<font face="courier new, monospace">-- ends up being a type error for the implementation you gave</font></div><div class="gmail_extra"><span style="font-size:13px"><font face="courier new, monospace">victoryRule' :: ReadExp m => m ()</font></span></div>

<div class="gmail_extra"><font face="courier new, monospace">...</font></div><div class="gmail_extra"><br></div><div class="gmail_extra" style><font face="arial, helvetica, sans-serif">And nicely, you can still use both of them in more general computations that also need write access.</font></div>

<div class="gmail_extra"><br><div class="gmail_quote">On Tue, Jan 28, 2014 at 6:03 AM, Corentin Dupont <span dir="ltr"><<a href="mailto:corentin.dupont@gmail.com" target="_blank">corentin.dupont@gmail.com</a>></span> wrote:<br>

<blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left-width:1px;border-left-color:rgb(204,204,204);border-left-style:solid;padding-left:1ex"><div dir="ltr"><div><div><div>Hi Haskell-Caféists!<br>I have a small DSL for a game. Some instructions have effects (change the game state), some not.<br>

-> In short, my question is: how can I semantically separate instructions with effect from the others? i.e. how can I mark down and track those effects?<br>
<br>Here is a simplified version of the DSL I use.<br>First some boilerplate:<br><span style="font-family:'courier new',monospace"><br>> {-# LANGUAGE GADTs #-}<br>> import Control.Monad<br>> import Control.Monad.State<br>


> import Control.Monad.Free<br></span><br>This is the DSL:<br><br><span style="font-family:'courier new',monospace">> data Exp a where<br>>   ReadAccount  :: Exp Int<br>>   WriteAccount :: Exp Int -> Exp () <br>


>   SetVictory   :: Exp Bool -> Exp ()<br>>   OnTimer      :: Exp () -> Exp ()<br>>   Return       :: a -> Exp a<br>>   Bind         :: Exp a -> (a -> Exp b) -> Exp b</span><br><br>It can read and write to an account (belonging to the state of the game), set a victory condition, and trigger some event every minute.<br>


<br><span style="font-family:'courier new',monospace">> instance Monad Exp where<br>>    return = Return<br>>    (>>=)  = Bind<br><br>> instance Functor Exp where<br>>    fmap f e = Bind e $ Return . f</span><br>


<br>With that you can write:<br><br><span style="font-family:'courier new',monospace">> victoryRule :: Exp ()<br>> victoryRule = SetVictory $ do<br>>   m <- ReadAccount<br>>   return (m > 100)</span><br>

<br>
"victoryRule" sets the victory condition to be: "if there is more than 100 gold in the account, you win."<br><br>This is the game state:<br><br><span style="font-family:'courier new',monospace">> data Game = Game { bankAccount :: Int,<br>


>                    victory     :: Exp Bool,<br>>                    timerEvent  :: Exp ()}<br></span><br>The evaluation of "Exp" can be:<br><br><span style="font-family:'courier new',monospace">> eval :: Exp a -> State Game a<br>


> eval  (SetVictory v) = modify (\g -> g{victory = v})<br>> eval ReadAccount = get >>= return . bankAccount<br>> eval _ = undefined -- etc.</span><br><br>If you evaluate "victoryRule", you change the Game state by setting the victory field. Then, each time you will evaluate the victory field, you will know if you won or not (depending on your account...).<br>


This is all well and good, but imagine if you write:<br><span style="font-family:'courier new',monospace"><br>> victoryRule' :: Exp ()<br>> victoryRule' = SetVictory $ do<br>>   m <- ReadAccount<br>

>   WriteAccount (return $ m + 1)<br>
>   return (m > 100)</span><br><br>Ho no! Now each time a player is refreshing his screen (on the web interface), the victory condition is re-evaluated to be displayed again, and the bank account is increased by 1!<br>


This is not what we want. We should allow only effect-less (pure) instructions in the victory field, like readAccount, but not WriteAccount.<br><br>How would you do that?<br><br>I tried with the Free monad to delimit those effects.<br>


I re-write each primitives, marking them with the special type "Effect", when needed.<br><span style="font-family:'courier new',monospace"><br>> type Effect = Free Exp<br><br>> -- readAccount remain the same: it has no effect <br>


> readAccount :: Exp Int <br>> readAccount = ReadAccount<br><br>> --writeAccount is marked as having an effect<br>> writeAccount :: Exp Int -> Effect (Exp ())<br>> writeAccount ei = Pure $ WriteAccount ei<br>


<br>> --onTimer is able to trigger an effect every minute<br>> onTimer :: Effect (Exp ()) -> Effect (Exp ())<br>> onTimer e = Pure $ OnTimer $ iter join e<br><br>> --victoryRule can be re-written like this, note that effects are rejected now<br>


> victoryRule'' :: Effect (Exp ())<br>> victoryRule'' = Pure $ SetVictory $ do<br>>   m <- readAccount<br>>   --writeAccount (return $ m + 1) --will be rejected at compile time (good)!<br>>   return (m > 100)<br>


<br>> --increase my bank account by 1 every minute<br>> myTimer :: Effect (Exp ())<br>> myTimer = onTimer $ do<br>>   m <- lift readAccount   <br>>   writeAccount (return $ m + 1)<br></span><br>I don't know if I got it right at all... How does it sound?<br>


It only bothers me that in this context "Pure" really means "Impure" :)<br></div>Do you think of any other solution? <br><br></div>Cheers,<br></div>Corentin<br></div>
<br>_______________________________________________<br>
Haskell-Cafe mailing list<br>
<a href="mailto:Haskell-Cafe@haskell.org">Haskell-Cafe@haskell.org</a><br>
<a href="http://www.haskell.org/mailman/listinfo/haskell-cafe" target="_blank">http://www.haskell.org/mailman/listinfo/haskell-cafe</a><br>
<br></blockquote></div><br></div></div></div>