I'm trying to practice some functional techniques. I haven't worked much in Haskell, so any language-specific tips would be very appreciated, but what I care for even more are general tips towards my approach that I can carry between functional languages. Performance is not a big concern of mine right now.
I implemented the winner-checking algorithm for the connect-four game. Basically, given a 6x7 grid, I need to be able to determine if a player has successfully placed four consecutive tiles down (horizontally, vertically, or diagonally). If so, I need to return which player it was.
Here is my implementation:
module Logic (Player(..), Board, getWinner) where import qualified Control.Monad as Monad data Player = Player1 | Player2 type BoardEntry = Maybe Player type Board = [[BoardEntry]] type Coord a = (a, a) instance Eq Player where Player1 == Player1 = True Player2 == Player2 = True x == y = False map2dList :: (a -> b) -> [[a]] -> [[b]] map2dList fn = map (map fn) getElementAt :: [a] -> Int -> Maybe a getElementAt (el:_) 0 = Just el getElementAt (_:rest) n = getElementAt rest (n - 1) getElementAt  _ = Nothing getBoardEntryAt :: Board -> Coord Int -> Maybe BoardEntry getBoardEntryAt board (x, y) = getElementAt board y >>= flip getElementAt x getBoardDimensions :: Board -> (Int, Int) getBoardDimensions board = ((length . head) board, length board) addCoords :: Coord Int -> Coord Int -> Coord Int addCoords (x1, y1) (x2, y2) = (x1 + x2, y1 + y2) isCoordInRegion :: (Int, Int) -> Coord Int -> Bool isCoordInRegion (width, height) (x, y) = x >= 0 && x < width && y >= 0 && y < height getAllCoordsInRegion :: (Int, Int) -> [Coord Int] getAllCoordsInRegion (width, height) = [(x,y) | x <- [0..width-1], y <- [0..height-1]] getWinner :: Board -> Maybe Player getWinner board = let fourInRowsFromOrigin = [ [(0, 0), (1, 0), (2, 0), (3, 0)], [(0, 0), (1, 1), (2, 2), (3, 3)], [(0, 0), (0, 1), (0, 2), (0, 3)], [(0, 0), (-1, 1), (-2, 2), (-3, 3)] ] playerPieceAt = Monad.join . getBoardEntryAt board results = concat $ do coord <- (getAllCoordsInRegion . getBoardDimensions) board fourInRow <- map2dList (addCoords coord) fourInRowsFromOrigin player <- [Player1, Player2] return [player | all ((== Just player) . playerPieceAt) fourInRow] in case results of [winner] -> Just winner  -> Nothing _ -> undefined
And here is a sample driver, to help see that it works:
import Logic (Player(Player1, Player2), Board, getWinner) getWinnerAsString :: Maybe Player -> [Char] getWinnerAsString Nothing = "No one has won" getWinnerAsString (Just Player1) = "Player 1" getWinnerAsString (Just Player2) = "Player 2" printWinner :: Board -> IO () printWinner = putStrLn . getWinnerAsString . getWinner exampleBoard :: Board exampleBoard = let __ = Nothing p1 = Just Player1 p2 = Just Player2 in [ [__, __, __, __, __, __, __], [__, __, __, __, __, __, __], [__, __, p2, p1, __, __, __], [__, __, p1, p2, __, __, __], [__, p1, p1, p2, p1, __, __], [p1, p2, p2, p2, p1, __, __] ] main :: IO () main = printWinner exampleBoard