Puzzle game solver via Backtracking I'm relatively new to Haskell and I'd like to get feedback on the style of my program. Specifically: 


Coding Style: Can any parts be written in a more concise or more readable way? 
Misuse: Are there any parts that are generally should be avoided, or any typical beginner mistakes? (Basically the do's and don't's)


The game

The program solves a puzzle that is also known as binoxxo: There is a 2n x 2n square grid that is partially filled with X and O. The goal filling each cell of the grid with an X or O such that the result satisfies three conditions:


In each column and each row, the same symbol cannot occur more than twice in a row. (I.e. OXXOXO is ok, however OXXXOO is not.)
Each row and each column have exactly n Xs and n Os.
No two rows are equal and no two columns are equal.


(An online version can be found here.)

The program

The following module consists of three parts: First we define the new type to describe the grid, with the type constructors X,O and finally E (for an empty cell). Then we have two functions: isValid checks whether all three rules hold so far, and backtrack which actually solves the puzzle. It does so by placing an X and O in the first E spot of the given puzzle checking the validity of the new grids, and if that succeeds, recursing a step deeper. This function finds all the possible solutions, but due to the lazy evaluation we can use take 1 $ backtrack myBoard to get only one and finish a little bit quicker.

module Binoxxo (isValid, backtrack, exampleBoard, Cell (..), Board) where

import Control.Applicative
import Data.List

data Cell = E | X | O -- E = empty
  deriving (Eq,Show)

type Board = [[Cell]]

exampleBoard = -- for solving: call "backtrack exampleBoard"
  [[X,X,E,E],
   [E,E,E,E],
   [O,E,E,X],
   [O,O,E,X]]

-- backtracking
backtrack :: Board -&gt; [Board]
backtrack b
  | isFull b = [b] --board has no more empty cells
  | otherwise = nub $ concat $ map backtrack validBoards
  where
    isFull b = not $ E `elem` concat b
    newBoards = generateAllBoards b :: [Board] 
    validBoards = filter isValid newBoards

    generateAllBoards :: Board -&gt; [Board] -- adds one new X/O in the position of a E
    generateAllBoards b = concat $ map assembleBoards (prefixRowSuffix b)
      where 
        prefixRowSuffix :: [a] -&gt; [([a],a,[a])]-- [1,2,3,4] -&gt;  [([],1,[2,3,4]), ([1],2,[3,4]), ([1,2],3,[4]), ([1,2,3],4,[])]
        prefixRowSuffix b = zip3 (inits b) b (drop 1 $ tails b)

        assembleBoards :: ([[Cell]],[Cell],[[Cell]]) -&gt; [Board]
        assembleBoards (front,m,back) = take 2 -- we only need to place X and O in the first occurence of E, because one of them MUST be correct
          [front ++[f++[x]++b]++ back |
            (f,E,b)&lt;-prefixRowSuffix m,x&lt;-[X,O]]

-- validity check (implement the three rules)
isValid :: Board -&gt; Bool
isValid b = and $ 
    [and . map checkNeighbours, checkDupli, and . map checkCount] -- each of these get applied to all normal and transposed board
     &lt;*&gt; [rows, cols] 
  where
    rows = b :: Board
    cols = transpose b :: Board

    -- we cannot have three consecutive X or O
    checkNeighbours :: [Cell] -&gt; Bool
    checkNeighbours (a:b:c:xs) = 
       let this = not $ any ((&amp;&amp; a==b &amp;&amp; b==c) . (c==)) [X,O] 
           rest = checkNeighbours (b:c:xs)
       in this &amp;&amp; rest
    checkNeighbours _  = True

    -- we cannot have two equal rows/columns
    checkDupli :: Board -&gt; Bool
    checkDupli b = check $ filter (all (/=E)) b -- only check full rows for duplicates
      where check (x:xs) =
              (not $ x `elem` xs)  &amp;&amp; check xs
            check [] = True

    -- if row is of length n, we can have at most n/2 X and O
    checkCount :: [Cell] -&gt; Bool
    checkCount xs = notTooMany O &amp;&amp; notTooMany X
      where 
        len = length xs
        notTooMany xo = len &gt;= 2 * length (filter (==xo) xs)
isFull fires when assembleBoards returns an empty list; we'd expect to be able to ask that question only once. newBoards and validBoards do not deserve names - if you want the reader to be able to tell what the value means, comments are more appropriate.

Most of the rest of backtrack is about descending into a nested data structure and changing a small part, which lens specializes in: traverse . traverse . filtered (==E) descends into the board, then each of its elements, then each of their E cells. holesOf gives you, roughly speaking, the positions of the targets in the original board - it separates the board into a Cell and a Cell -&gt; Board for each target. peek lets you forget there was an E.

import Control.Comonad.Representable.Store (peek)
import Control.Lens (holesOf, filtered)

backtrack :: Board -&gt; [Board]
backtrack b = case setFirstE b of
  -- board has no more empty cells
  Nothing -&gt; [b]
  -- we only need to place X and O in the first occurence of E, because one of them MUST be correct
  Just setter -&gt; nub $ concatMap backtrack $ filter isValid $ map setter [X,O]

-- Punches the first E out of the board, if any.
setFirstE :: Board -&gt; Maybe (Cell -&gt; Board)
setFirstE = listToMaybe . map (flip peek) . holesOf (traverse . traverse . filtered (==E))