Beginner functional programmer... (but not beginner programmer)

Currently, I have the following code:

import Control.Monad (mapM_)

main = gridMaker 3

gridMaker :: Int -> IO ()
{-Creates a nxn matrix filling the lower triangle with "O"'s
  and filling the rest with "X"s
  O --- X --- X
  O --- O --- X
  O --- O --- O
gridMaker gridSize =
  -- Creates the indicators
  let startingIter = 1
      indicators = indicatorListCreator gridSize startingIter
  -- Print each indicator out to IO
  in mapM_ linePrinter indicators

indicatorListCreator :: Int -> Int -> [[String]]
{- Build the indicators of
   [["O", "X", "X"],["O", "O", "X"] ... and so forth.
   Recursively determines how many iterations we've been through,
   and determines how many "X"s and "O"s we should have
   per each line. -}
indicatorListCreator gridLen iterNum
  | iterNum > gridLen = []
  | otherwise =
    let itersRemaining = gridLen - iterNum
        indicator = replicate iterNum "O" ++
                    replicate itersRemaining "X"
      indicator: indicatorListCreator gridLen (iterNum + 1)

linePrinter :: [String] -> IO ()
{- Takes the indicators and prints each line accordingly. -}
linePrinter [indicator1, indicator2, indicator3] =
  let between = " --- "
      outString = indicator1 ++ between ++
                  indicator2 ++ between ++
  in putStrLn outString
linePrinter _ = error"Stupidly hardcoded to only show 3x3"

Running and compiling this code results in:

O --- X --- X
O --- O --- X
O --- O --- O

Some of my thoughts...

  • Is it possible to build indicatorListCreator out of folds?
  • How can I circumvent hardcoding linePrinter
  • Is this optimal?
  • Where should I improve on coding conventions / style?
  • Any other glaring shortcomings?

Thank you in advance!


I like that you are trying to not use explicit recursions everywhere. That being said, there are a few standard functions that we could use to get a code similar to the following.

import Data.List(intercalate)

grid :: Int -> [String]
grid n = [intercalate " --- " . take n . drop (n-k) $ osxs | k <- [1..n]]
    where osxs = replicate n "O" ++ replicate n "X"

printGrid :: Int -> IO ()
printGrid = putStr . unlines . grid

Most notably, Data.List.intercalate can be used to generalize your linePrinter, and unlines helps us avoid mapM_.

If you are writing a game, then a Data.Map.Strict.Map could be useful for storing the board's current values.

  • \$\begingroup\$ Thank you so much! Amazing how using the right functions can shorten code length by a huge chunk. take n . drop (n-k) is super neat and clever! \$\endgroup\$
    – gust
    May 27 '20 at 13:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.