[Haskell-cafe] Hangman game
gale at sefer.org
Sun Jan 20 14:03:31 EST 2008
You gave some suggestions of other styles of Haskell programming
that Ronald could try for his program. These styles are definitely
worth knowing, so if Ronald is not familiar with them, he may want
to try them out. However, in most cases, I think what Ronald
already did is nicer than what you are suggesting.
Paul Johnson wrote:
> The design reads very much like a straight translation
> from the imperative style, which is why so much of it is in the IO
> monad. There is nothing wrong with this for a simple game like Hangman,
> but for larger games it doesn't scale.
It's a state monad, and most of his code is in that style. It doesn't
read to me like imperative style at all. And it scales beautifully.
There is a lot of liftIO, because that is the bulk of the work in
this program. But Ronald cleanly separated out the game logic,
and even the pure parts of the UI logic, into pure functions
like "handleGuess" and "renderGameState". I personally might
have kept a more consistently monadic style by writing those
as pure monads, like:
handleGuess :: MonadState GameState m => Char -> m ()
renderGameState :: MonadState GameState m -> m String
In certain situations, that approach gives more flexiblity.
Like for refactoring, or adding new features. But Ronald's
approach is also very nice, and I might also do that.
> 1: Your GameState type can itself be made into a monad.
Yes, it can. But StateT GameState IO is the perfect
monad for this game - writing a new monad would just be
reinventing the wheel. It would certainly be a good learning
experience for understanding the inner workings of the
state monad transformer, though.
> 2: You can layer monads by using monad transformers. Extend the
> solution to part 1 by using StateT IO instead of just State.
I think he is already using that type.
> 3: Your current design uses a random number generator in the IO monad.
> Someone already suggested moving that into the GameState. But you can
> also generate an infinite list of random numbers. Can you make your
> game a function of a list of random numbers?
He could, but I would advise against that technique. In more
complex games, you may need to do many different kinds of
random calculations in complex orders. Keeping a random generator
inside a state monad is perfect for that. And since Ronald already
set up the plumbing for the state monad, he is already home.
Generating an infinite list from a random generator "burns up"
the generator, making it unusable for any further calculations.
Sometimes that doesn't matter, but I think it's a bad habit. I admit
you'll catch me doing it sometimes though, in "quick and dirty"
situations like at the GHCi prompt.
> 4: User input can also be considered as a list. Haskell has "lazy
> input", meaning that you can treat user input as a list that actually
> only gets read as it is required. Can you make your game a function of
> the list of user inputs? How does this interact with the need to
> present output to the user? What about the random numbers?
That type of "lazy IO" is considered by many to be one of Haskell's
few warts. It is a hole in the type system that lets a small amount of
side-effects leak through, and even that small amount leads to bugs.
In many situations it's hard to avoid without making a wreck out
of your whole program structure (though more and more tools
are becoming available to help, such as the ByteString stuff).
Ronald did great without it - why resort to that?
All that said - this is clearly a matter of taste, of course.
Thanks for bringing up a variety of approaches.
More information about the Haskell-Cafe