
On Thu, 2010-04-29 at 14:49 -0700, Hein Hundal wrote:
--- On Thu, 4/29/10, Maciej Piechotka
wrote: Hein Hundal wrote:
I figured I should try a larger program
converting one of my Mathematica programs, a simulator for the card game Dominion, over to Haskell. Most of
in Haskell, so I am that is going well
except for one patch of imperative code. My Haskell version of this code is ugly. I was hoping someone could recommend a better way to do it. I will paste a simplified version of the code below. If necessary, I can provide all the other code used for
1. Use strong typing. Or any typing
I simplified the code for the post. In the real version, I use strong typing. The Card type is enumerated. I have been using [Card] instead of calling it a Deck. I could change that.
Deck is not important - it's rather eye candy ;) [Card] is as clear as Deck.
3. Don't use too much variables. 6-8 is probably the maximum you should deal with (human short-term memory holds about 5-10 points of entry. Split functions into smaller functions (even in where).
I do have to get the information into the functions, so the only way I can avoid having lots of variables is by introducing new structures. I can do that.
New structures annotates types easily. data ComplicatedData = ComplicatedData { turn :: Int, deck :: [Cards], ... } There is syntax sugar: doSomething :: ComplicatedData -> [Cards] doSomething cd = drop (turn cd) (deck cd) -- takes deck drops as meny cards as turns passed and returns it doSomethingCrazy :: ComplicatedData -> ComplicatedData doSomethingCrazy cd = cd {deck = drop (turn cd) (deck cd)} -- creates new ComplicatedData which have everything as -- the argument except that desk is missing as many cards -- as turn currently is
6. While Haskell have long tradition of having short namesit is not always good (see 3). Use them only if you are sure it is clear what they mean:
In the original version, I had longer variable names where they seemed necessary.
The main sources of ugliness are the long lists of variables. Every time I call doAct or construct a LoopState variable, I am repeating all those variables. I will try changing the type of doAct to
doAct :: LoopState -> LoopState
Cheers, Hein
See the record syntax. Or refactor it to use a helper functions. Depending on purpose and advancement you can play with pointless style. Regards PS. Consider using helper functions. Even if they are longer: func n | 128 `mod` n == 0 = 3 | otherwise = 2 vs. func n | n `divides` 128 = 3 | otherwise = 2 k `divides` n = n `mod` k == 0 In first example you have to think what I meant. In second it is self-commention (n `divides` 128 - it is just n divides 128 with strange apostrophes). doSomething (State (c:cs) t ph ta ...) | c == Ace `of` Hearths = State cs (t+1) (c:ph) ta ... | otherwise = State cs (t+1) ph (c:ta) ... vs. putOnTable :: Card -> State -> State putOnTable c s = s {table = c:table s} putIntoPlayerHand :: Card -> State -> State putIntoPlayerHand c s = s {playerHand = c:playerHand s} drawCard :: State -> (Card, State) drawCard s = let (c:cs) = deck s in (x, s {deck = s}) nextTurn :: State -> State nextTurn s = s {turn = turn s + 1} doSomething s | c == Ace `of` Hearths = nextTurn $ putIntoPlayerHand c s' | otherwise = nextTurn $ putOnTable c s' where (c, s') = drawCard s or doSomething s = let (c, s') = drawCard s s'' | c == Ace `of` Hearths = putIntoPlayerHand c s' | otherwise = putOnTable c s' in nextTurn s'' Regards PS. For 'advanced' only - and many advanced users dislikes this approach and would recommend not to use it. Anyway - don't bother with it until later if you don't understands. data GameState = GameState { deck :: [Card], turn :: Int, playerHand :: [Card], table :: [Card], ... } putOnTable :: Card -> State GameState () putOnTable c = modify (\s -> s {table = c:table s}) putIntoPlayerHand :: Card -> State GameState () putIntoPlayerHand c = modify (\s -> s {playerHand = c:playerHand s}) drawCard :: State GameState Card drawCard = do (c:cs) <- gets deck modify (\s -> s {deck = cs}) return c nextTurn :: State GameState () nextTurn = modify (\s -> s {turn = turn s + 1}) doSomething :: State GameState () doSomething = do c <- drawCard if c == Ace `of` Hearths then putIntoPlayerHand c else putOnTable c nextTurn