Connect-Four winner-checking algorithm 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 -&gt; b) -&gt; [[a]] -&gt; [[b]]
map2dList fn = map (map fn)

getElementAt :: [a] -&gt; Int -&gt; Maybe a
getElementAt (el:_) 0 = Just el
getElementAt (_:rest) n = getElementAt rest (n - 1)
getElementAt [] _ = Nothing

getBoardEntryAt :: Board -&gt; Coord Int -&gt; Maybe BoardEntry
getBoardEntryAt board (x, y) =
  getElementAt board y &gt;&gt;= flip getElementAt x

getBoardDimensions :: Board -&gt; (Int, Int)
getBoardDimensions board =
  ((length . head) board, length board)

addCoords :: Coord Int -&gt; Coord Int -&gt; Coord Int
addCoords (x1, y1) (x2, y2) =
  (x1 + x2, y1 + y2)

isCoordInRegion :: (Int, Int) -&gt; Coord Int -&gt; Bool
isCoordInRegion (width, height) (x, y) =
  x &gt;= 0 &amp;&amp; x &lt; width &amp;&amp;
  y &gt;= 0 &amp;&amp; y &lt; height

getAllCoordsInRegion :: (Int, Int) -&gt; [Coord Int]
getAllCoordsInRegion (width, height) =
  [(x,y) | x &lt;- [0..width-1], y &lt;- [0..height-1]]

getWinner :: Board -&gt; 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 &lt;- (getAllCoordsInRegion . getBoardDimensions) board
      fourInRow &lt;- map2dList (addCoords coord) fourInRowsFromOrigin
      player &lt;- [Player1, Player2]
      return [player | all ((== Just player) . playerPieceAt) fourInRow]
  in case results of
    [winner] -&gt; Just winner
    [] -&gt; Nothing
    _ -&gt; undefined

And here is a sample driver, to help see that it works:
import Logic (Player(Player1, Player2), Board, getWinner)

getWinnerAsString :: Maybe Player -&gt; [Char]
getWinnerAsString Nothing = &quot;No one has won&quot;
getWinnerAsString (Just Player1) = &quot;Player 1&quot;
getWinnerAsString (Just Player2) = &quot;Player 2&quot;

printWinner :: Board -&gt; 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
All the indexing can be deleted. You only need the transpose function plus a custom function that grabs the diagonals of a matrix (list of lists, all of the same length).
import Data.List (transpose)

-- | The 'diagonals' function returns the diagonals of a matrix
diagonals :: [[a]] -&gt; [[a]]
diagonals matrix = lowerDiags matrix ++ upperDiags matrix
    where lowerDiags = reverse . transpose . zipWith drop [1..]
          upperDiags = transpose . zipWith drop [0..] . transpose

You can invoke it as shown below. If any of the vectors has four in a row, you have a winner.
board = ...
vectors = board ++ transpose board ++ diagonals board ++ diagonals (reverse board)