haskell - Best Practices for Where clauses -


i wrote simple tic-tac-toe program in haskell. runs on command line, has 1 , 2 player mode, , implements minimax algorithm when play against it.

i'm used writing proper code in oo languages, haskell new me. code works reasonably well, seems hard read (even me!). suggestions on how make code more...haskellian?

import data.list import data.char import data.maybe import control.monad  data square = | b | c | d | e | f | g | h | | x | o deriving (read, eq, ord) instance show square    show = "a"     show b = "b"     show c = "c"     show d = "d"     show e = "e"     show f = "f"     show g = "g"     show h = "h"     show = "i"     show x = "x"     show o = "o" type row = [square] type board = [row] data player = px | po deriving (read, eq) instance show player    show px = "player x"    show po = "player o" data result = xwin | tie | owin deriving (read, show, eq, ord)   main :: io () main =     putstrln "let's play tic tac toe!!!"     putstrln "yeeeaaaaaahh!!!"     gameselect  gameselect :: io () gameselect =     putstrln "who gonna play, 1 playa or two??? (enter 1 or 2)"     gamemode <- getline     case gamemode of "1" -> oneplayermode                      "2" -> twoplayermode                      gamemode -> gameselect     oneplayermode =              putstrln "one playa"              putstrln "cool!  ready play...against invincible tic tac toe ai!!!!! hahahaha!!!"              gameloop 1 emptyboard px           twoplayermode =              putstrln "two players"              gameloop 2 emptyboard px           emptyboard = [[a,b,c],[d,e,f],[g,h,i]]  gameloop :: int -> board -> player -> io () gameloop noofplayers board player =     case detectwin board of xwin -> endgame board xwin                             owin -> endgame board owin                             tie -> endgame board tie                             nothing -> if noofplayers == 1                                        if player == px                                              entermove 1 board player                                              else enterbestmove board po                                        else entermove 2 board player  entermove :: int -> board -> player -> io ()  entermove noofplayers board player =      displayboard board      if noofplayers == 1      putstrln ("make move. (a-i)")      else putstrln (show player ++ ", it's turn. (a-i)")      move <- getline      print move      if not $ move `elem` ["a","b","c","d","e","f","g","h","i"]                      putstrln $ move ++ " not move, doofus"             gameloop noofplayers board player          else if (read (map toupper move) :: square) `elem` [ sq | sq <- concat board]                            gameloop noofplayers (newboard (read (map toupper move) :: square) player board) (if player == px po else px)             else                putstrln "that square occupied"                gameloop noofplayers board player  enterbestmove :: board -> player -> io () enterbestmove board player = gameloop 1 (newboard bestmove player board) px     bestmove = fst $ findbestmove po board           findbestmove :: player -> board -> (square, result)           findbestmove player board             | player == po = findmax results             | player == px = findmin results             findmin = foldl1 (\ acc x -> if snd x < snd acc x else acc)                   findmax = foldl1 (\ acc x -> if snd x > snd acc x else acc)                   results = [ (sq, getresult b) | (sq, b) <- boards player board ]                   getresult b = if detectwin b == nothing                                  snd (findbestmove (if player == px po else px) b)                                  else fromjust $ detectwin b                   boards :: player -> board -> [(square, board)]                   boards player board = [(sq, newboard sq player board) | sq <- concat board, sq /= x, sq /=o]  displayboard :: board -> io () displayboard board =     mapm_ print board  newboard :: square -> player -> board -> board newboard move player board = [ [if sq == move mark else sq | sq <- row] | row <- board]     mark = if player == px x else o  detectwin :: board -> (maybe result) detectwin board    | [x,x,x] `elem` board ++ transpose board = xwin    | [x,x,x] `elem` [diagonal1 board, diagonal2 board] = xwin    | [o,o,o] `elem` board ++ transpose board = owin    | [o,o,o] `elem` [diagonal1 board, diagonal2 board] = owin    | [x,x,x,x,x,o,o,o,o] == (sort $ concat board) = tie    | otherwise = nothing         diagonal1 :: board -> [square]      diagonal1 bs = bs!!0!!0 : bs!!1!!1 : bs!!2!!2 : []      diagonal2 :: board -> [square]      diagonal2 bs = bs!!0!!2 : bs!!1!!1 : bs!!2!!0 : []  endgame :: board -> result -> io () endgame board result =     displayboard board     if result `elem` [xwin, owin]                      let player = if result == xwin px else po             in                  putstrln ("the game over, , " ++ show player ++ " wins!")                 putstrln ((if player == px show po else show px) ++ " loser lol")         else             putstrln "the game tie"             putstrln "you both losers!  ugh!"     putstrln "want play again? (y/n)"     again <- getline     if again `elem` ["y", "y", "yes", "yes", "yes"]          gameselect          else             putstrln "goodbye" 

edit: special @chi , @caridorc, i've made following changes. further suggestions considered , updated well

import data.list import data.char import data.maybe import control.monad  data square = | b | c | d | e | f | g | h | | x | o deriving (read, eq, ord) instance show square    show = "a"     show b = "b"     show c = "c"     show d = "d"     show e = "e"     show f = "f"     show g = "g"     show h = "h"     show = "i"     show x = "x"     show o = "o" type row = [square] type board = [row] data player = px | po deriving (read, eq) instance show player    show px = "player x"    show po = "player o" data result = xwin | tie | owin deriving (read, show, eq, ord)   main :: io () main =     putstrln "let's play tic tac toe!!!"     putstrln "yeeeaaaaaahh!!!"     gameselect  gameselect :: io () gameselect =     putstrln "who gonna play, 1 playa or two??? (enter 1 or 2)"     gamemode <- getline     case gamemode of        "1" -> oneplayermode       "2" -> twoplayermode       _ -> gameselect     oneplayermode =              putstrln "one playa"              putstrln "cool!  ready play...against invincible tic tac toe ai!!!!! hahahaha!!!"              gameloop 1 emptyboard px           twoplayermode =              putstrln "two players"              gameloop 2 emptyboard px           emptyboard = [[a,b,c],[d,e,f],[g,h,i]]  displayboard :: board -> io () displayboard board =     mapm_ print board  otherplayer :: player -> player otherplayer px = po otherplayer po = px  gameloop :: int -> board -> player -> io () gameloop noofplayers board player =     case detectwin board of        res -> endgame board res       nothing -> case noofplayers of                    1 -> case player of                           px -> entermove 1 board player                           po -> enterbestmove board po                     2 -> entermove 2 board player  entermove :: int -> board -> player -> io ()  entermove noofplayers board player =      displayboard board      case noofplayers of        1 -> putstrln ("make move. (a-i)")        2 -> putstrln (show player ++ ", it's turn. (a-i)")      move <- getline      print move      if not $ move `elem` ["a","b","c","d","e","f","g","h","i"]         putstrln $ move ++ " not move, doofus"         gameloop noofplayers board player      else if (read (map toupper move) :: square) `elem` (concat board)             gameloop noofplayers (newboard (read (map toupper move) :: square) player board) (otherplayer player)           else             putstrln "that square occupied"             gameloop noofplayers board player  enterbestmove :: board -> player -> io () enterbestmove board player = gameloop 1 (newboard bestmove player board) px     bestmove = fst $ findbestmove po board  findbestmove :: player -> board -> (square, result)  -- minimax algorithm findbestmove player board   | player == po = findmax results   | player == px = findmin results   findmin = foldl1 (\ acc x -> if snd x < snd acc x else acc)              findmax = foldl1 (\ acc x -> if snd x > snd acc x else acc)         results = [ (sq, getresult b) | (sq, b) <- boards player board ]         getresult b = case detectwin b of                         nothing -> snd (findbestmove (otherplayer player) b)                         x -> x         boards :: player -> board -> [(square, board)]         boards player board = [(sq, newboard sq player board) | sq <- concat board, sq /= x, sq /=o]  newboard :: square -> player -> board -> board newboard move player board = [ [if sq == move mark else sq | sq <- row] | row <- board]     mark = if player == px x else o  detectwin :: board -> (maybe result) detectwin board    | [x,x,x] `elem` (triplets board) = xwin    | [o,o,o] `elem` (triplets board) = owin    | [x,x,x,x,x,o,o,o,o] == (sort $ concat board) = tie    | otherwise = nothing  triplets :: board -> [[square]] triplets board = board ++ transpose board ++ [diagonal1] ++ [diagonal2]         flat = concat board      diagonal1 = [flat !! 0, flat !! 4, flat !! 8]      diagonal2 = [flat !! 2, flat !! 4, flat !! 6]  endgame :: board -> result -> io () endgame board result =     displayboard board      putstrln $ endgamemessage result      putstrln "want play again? (y/n)"     again <- getline     if again `elem` ["y", "y", "yes", "yes", "yes"]      gameselect      else         putstrln "goodbye"  endgamemessage :: result -> string endgamemessage result    | result `elem` [xwin, owin] = winnernotice ++ losernotice    | otherwise = "the game tie\n" ++ "you both losers!  ugh!"         winner = case result of       xwin -> px       owin -> po      winnernotice = "the game over, , " ++ show winner ++ " wins!\n"      losernotice = (show $ otherplayer winner) ++ " loser lol" 

code style matter of personal preference, in haskell arguably more in other languages "standard" style guide. still, here's few random suggestions.


don't over-indent cases: use line

case gamemode of "1" -> oneplayermode                  "2" -> twoplayermode                  gamemode -> gameselect 

vs

case gamemode of    "1" -> oneplayermode    "2" -> twoplayermode    gamemode -> gameselect 

or even

case gamemode of    "1" -> oneplayermode    "2" -> twoplayermode    _   -> gameselect 

case preferred if .. == constructor:

if player == px  entermove 1 board player  else enterbestmove board po 

vs

case player of    px -> entermove 1 board player     py -> enterbestmove board po 

i'd recommend against using partial functions fromjust, since can crash program if forget check nothing beforehand. safer alternatives exist, never cause such crashes -- less burden on programmer.

if detectwin b == nothing  snd (findbestmove (if player == px po else px) b)  else fromjust $ detectwin b 

vs

case detectwin b of    nothing -> snd $ findbestmove (if player == px po else px) b    x  -> x 

or

frommaybe (snd $ findbestmove (if player == px po else px) b)   $ detectwin b 

try factorize commonly used functions. instance

nextplayer px = po nextplayer po = px 

can replace uses of

if player == px po else px 

no do needed when there's 1 statement:

if noofplayers == 1 putstrln ("make move. (a-i)")    -- no need parentheses here else putstrln (show player ++ ", it's turn. (a-i)") 

since mention where in title, let me state have mixed feelings where, in general. know tend avoid where in favor of let, feeling not shared many other haskellers, take care.

personally, tend limit where uses one-liners:

foo = f x y    x = ...          y = ... 

especially in do blocks, might span several lines, prefer lets:

foo =    line    line using x     -- x ??!?    line    ...    line   x = ...     -- ah, here 

vs

foo =    line    let x = ...    line using x    line    ...    line 

however, feel free adopt style find more readable.


also don't forget add few comments, @mawalker points out. definitions obvious , don't need explanation. others benefit few lines explaining purpose.


Comments

Popular posts from this blog

ruby - Trying to change last to "x"s to 23 -

jquery - Clone last and append item to closest class -

c - Unrecognised emulation mode: elf_i386 on MinGW32 -