[Haskell-cafe] Anyone mind proofing a short monad transformers explanation?

2007-12-17 Thread Jack Kelly
I sent this to a friend who asked me about monad transformers and he 
reckons it should be saved somewhere. If anyone feels up to giving it a 
quick fact-check, that'd be great. I'm not sure what to do with it, 
though. It doesn't seem to be made of the right stuff for a tutorial but 
I'm not sure what to do with it.


--- SNIP ---

Basically it's like making a double, triple, quadruple, ... monad by
wrapping around existing monads that provide wanted functionality.

You have an innermost monad (usually Identity or IO but you can use
any monad). You then wrap monad transformers around this monad to make
bigger, better monads.

Concrete example: Suppose I was writing a server. Each client thread
must be of type IO () (that's because forkIO :: IO () - IO
ThreadID)).

Suppose also that this server has some configuration that (in an
imperative program) would be global that the clients all need to
query.

 data Config = Config Foo Bar Baz

One way of doing this is to use currying and making all the client
threads of type Config - IO (). Not too nice because any functions
they call have to be passed the Config parameter manually. The Reader
monad solves this problem but we've already got one monad. We need to
wrap IO in a ReaderT. The type constructor for ReaderT is ReaderT r m
a, with r the shared environment to read from, m the inner monad and a
the return type. Our client_func becomes:

 client_func :: ReaderT Config IO ()

We can then use the ask, asks and local functions as if Reader was the
only Monad:

(these examples are inside do blocks)

 p - asks port

(Assuming some function port :: Config - Int or similar.)

To do stuff in IO (or in the general case any inner monad) the liftIO
function is used to make an IO function work in this wrapped space:

(given h :: Handle, the client's handle)

 liftIO $ hPutStrLn h You lose
 liftIO $ hFlush h

IO is once again special. For other inner monads, the lift function
does the same thing. Note also that IO has no transformer and must
therefore always be the innermost monad.

This is all well and good, but the client_func now has type ReaderT
Config IO () and forkIO needs a function of type IO (). The escape
function for Reader is runReader :: Reader r a - r - a and similarly
for ReaderT the escape function is runReaderT :: ReaderT r m a - r -
m a:

(Given some c :: Config that's been assembled from config files or the
like)

 forkIO (runReaderT client_func c)

Will do the trick.

Monad transformers are like onions. They make you cry but you learn to
appreciate them. Like onions, they're also made of layers. Each layer
is the functionality of a new monad, you lift monadic functions to get
into the inner monads and you have transformerised functions to unwrap
each layer. They're also like a present in that regard: in this
example we unwrapped the outer wrapping paper to get to the present:
an object of type IO (), which lets us make haskell do something.

___
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe


Re: [Haskell-cafe] Implementing a MUD server in haskell

2007-12-16 Thread Jack Kelly

Jules Bean wrote:

Jack Kelly wrote: -snip-

Essentially you have to choose some form of identity (relational DB fans 
might call it a key) for your players and rooms. My gut feeling is 
that String will do fine; it's nice for debugging, gives rise to a 
natural way to write your admin commands and so on. However, it may not 
be quite good enough (100 mobs all called an orc need individual keys) 
and you may end up using an Int or Int64 for your key.


Perhaps doing the keying on an Int and then having a [String] of names 
that entities can be called is the way to go. List comprehensions will 
still work:


let ents = [ ent | ent - allents, name `elem` (Entity.aliases ent) ] in
-- ...

but so will addressing an object uniquely when needed:

let target = listToMaybe $ [ ent | ent - allents, id == (Entity.id ent) 
] in

-- ...
Once you've chosen a key, you can do something very like the C++ 
structure: -snip-


I'm thinking of doing the following (based on some suggestions from 
#haskell):


data Room = Room {...}
type Zone = Data.Map String Room
type World = Data.Map String Zone
type ZoneID = String
type RoomID = String
data Location = Location { zone :: ZoneID, room :: RoomID}
data Player = Player { location :: Location, ... }

Aside: When should I use newtype vs type? From what I understand, both 
create type aliases that are only relevant at compile time, but newtype 
disallows things like:


-- Bad
newtype Fred = Fred String
putStrLn $ Fred this won't work

as a rule of thumb, is it `better' to newtype things so that the type 
system can trip you up?



I should mention, actually, that there is also the naive solution:

 -snip-
That does seem like a bad idea.


There are always two techniques for maintaining invariants.
In solution 1, you would simply not store the player list in the room 
structure at all. Instead, you would only store the room in the player, 
and whenever you wanted to answer the question Which players are in 
this room you would do a quick search through all players to find them


playersInRoom :: Room - [Player]
playersInRoom r = [p | p - allplayers, (room p) == r]
-- Just like a database! In SQL we'd say:
-- SELECT p FROM allplayers WHERE p.room = r

Of course the disadvantage to this is the linear search through all the 
players. Depending on how often this is performed, this may actually 
cost you less that explicitly caching the value inside the room.


I'd not thought of doing it that way. Maybe because keeping a couple of 
pointers in sync in C is less painful then coding up a search to 
generate an appropriate list. Learning to stop looking through the 
imperative lens is hard.


In solution 2, of course, whenever you  move a player from room A to 
room B you do it via a function which is explicitly designed to keep 
everythign up to date:


movePlayer PlayerID - RoomID - RoomID - Game ()
movePlayer pid raid rbid = do
  p - getPlayer pid
  setPlayer pid (p {room = rbid})
  ra - getRoom raid
  rb - getRoom rbid
  setRoom raid (ra {players = (players ra)\\[pid]} )
  setRoom rbid (rb {players = pid : (players rb)} )

Which I've written in an imaginary Game monad, and using rather ugly 
low-level combinators get/set Room/Player. You could make it look much 
more pleasant in practice with some better chosen combinators.


Game looks like it'd behave like some kind of specialisation of State, 
but since there'd be responses sent out to the sockets, I'm thinking of 
something like StateT GameState IO (). Is it worth having the StateT 
because IO already handles some concept of state? If so, will running 
something like:


forkIO $ evalStateT gameLoop initialstate

work or will the IO actions be queued up until the end of gameLoop? The 
idea of building a Game monad is attractive regardless because I'll 
still need functions like movePlayer regardless of which path is chosen.


Still I think solution 1 (don't store redundant data which can get out 
of sync) has a lot to recommend it and solution 2 may be premature 
optimization; i.e. implement solution 2, which is a kind of caching of 
computed data, only once you've convinced yourself that recalculating 
that data every time really is slowing you down.


I think I'll go with solution 1. It's conceptually simpler and if it's 
slow I can profile and optimise later, right?


The simplest way to do this is to bundle all your big shared mutable 
world into a single MVar. What this amounts to is perfect brute force 
serialisation of the actual modification part: i.e. all world 
modifications share a global lock. This is easy to implement and easy to 
reason about.


I think that my fear of doing this was again instinctive premature 
optimisation.


If that turns out to be too restrictive, then you split up the MVars 
into smaller pieces, but then you have to think a bit harder to convince 
yourself it is safe.


Another idea that I picked up from the Simple TCP server 
(http://sequence.complete.org/node/258) is to have a single thread