Print numbers in a spiral from the center

#!/usr/bin/env stack
{- stack
--install-ghc
exec ghci
--package lens
-}

module Main where

import Control.Lens
import Data.List.Lens
import Debug.Trace
import Text.Printf
import Prelude hiding (Left, Right)

data Direction = Up | Left | Down | Right

data Location = Location Int Int

type Grid = [[Int]]

main = printClockwise 25

printClockwise :: Int -> IO ()
printClockwise n = putStrLn $concatMap printRow grid where grid = walk directions center 1 emptyGrid emptyGrid = replicate sq (take sq (repeat 0)) printRow row = concatMap (printf "%4s" . show) row ++ "\n" -- Ex. for a 5x5 spiral the start location is (2,2) center = Location half half half = floor$ (fromIntegral sq) / 2
-- This will return all directions we should walk through the spiral in.
-- Something like [Right, Down, Left, Left, Up, Up, Right, Right, Right]
directions = concatMap (\(direction, steps) -> take steps $repeat direction) (zip directionOrder steps) directionOrder = cycle [Right, Down, Left, Up] -- Ex. for a spiral up to 25, the steps are 1, 1, 2, 2, 3, 3, 4, 4, 5 steps = concatMap (take 2 . repeat) [1 .. sq - 1] <> [sq] -- Assume the input is a perfect square of an odd number sq = ceiling$ sqrt (fromIntegral n)

walk :: [Direction] -> Location -> Int -> Grid -> Grid
walk (dir : dirs) loc@(Location x y) current grid = do
let grid' = grid & (ix y . ix x) .~ current
let loc' = moveLocation dir loc
walk dirs loc' (current + 1) grid'
walk [] _ _ numbers = numbers

moveLocation Down (Location x y) = Location (x) (y + 1)
moveLocation Right (Location x y) = Location (x + 1) (y)
moveLocation Left (Location x y) = Location (x -1) (y)
moveLocation Up (Location x y) = Location (x) (y - 1)

I came across this problem on reddit and thought it'd be fun to try my hand at it in Haskell. The idea is to generate the grid before printing, and the insight to generate that grid was that the movement follows a predictable pattern, of right, down, left, up, and the # of steps taken in a given direction is 1, 1, 2, 2, 3, 3, 4, 4... , n - 1, n -1, n , where n is the square of the input number. So with that you can walk through the grid and fill in the numbers.

The function has no input checking, it assumes the input is a perfect square (of an odd number).

Not too advanced myself - so take with a grain of salt.

• Foremost I would separate the logic for constructing the grid and printing. Especially extract the function to construct the grid. Currently its wrapped in printClockwise IMHO it should be prominently its own function.
• There are a few definitions in printClockwise (emptyGrid, directionOrder and the printing) that I would consider reusable and would make them top level.
• You only use lens in one place. lens is quite an expensive import - there just was an interesting post. I suppose your intend was to learn lens and therefore it's used - but just for the code I would consider removing the dependency.
• the fixed "%4s" in printing - it's easy to estimate the required width for larger numbers. Or just make it that max n is 999.

Some stuff to consider that is really personal choice - no need to follow if you disagree:

• changing the data type to strict mutable array or similar.
• adding a walk that takes larger strides.
• split the walk function into one stream of positions in the spiral, and separately setting the values in a second step. This could solve the issue that the input needs to be a perfect square - just take the first n positions.

As last remarks:

• whenever I see such a spiral I think Ulam spiral. If you want to expand I would consider adding functionality to the printing to allow highlight - in this case for primes.
• I also checked whether the coordinates are on OEIS - they are A174344. And your algorithm agrees with the algorithm they post along side (the Julia implementation is almost the same, the other use some shenanigans with sin and cosine).