Reading String data into a Tree like nested grid structure (like Sudoku Board) Context

The original idea is to create an efficient grid structure for a Sudoku like board but this can be applied to many such grid structures (like wavelet transform on images, JPEG2000 etc). This particular one is basically a 9x9 Board which is made up of 3x3 Blocks where each Block is made up of 3 x Axis a a a type. You may think Axis type like a list limited to having only 3 elements (no more no less) and no empty or identiy element.

I have defined the Axis, Block and Board types as;

data Axis a =  Axis { _0 :: a
                    , _1 :: a
                    , _2 :: a
                    } deriving (Eq, Functor)
type Block a = (Axis(Axis a))
type Board a = (Axis(Axis (Block a)))

instance Show a =&gt; Show (Axis a) where
  showsPrec _ (Axis x y z) = shows x . (' ':) . shows y . (' ':) . shows z


As you notice, polymorphic Axis type is like a ternary tree with no leaves where the nodes can either be another Axis a type or an a type value. Does anybody know a general name for such data types in Haskell literature?

Anyways, by doing so, once i have a Board then i can easily access any Block or Cell quite efficiently. To access one of the 9 Blocks i can now simply do

_0 . _1 $ myBoard -- like (row 0, col 1) from myBoard


where to gain access to any cell within the Board all i have to do is to get to the Block and then to the cell like

_2 . _1 . _0 . _1 $ myBoard --  coord of cell . coord of Block =&gt; (_2 . _1) . (_0 . _1)


Problem

The problem arose when building up my Board type from a provided String. Since i post this here, I have done it but it smells like a fish and i think there must be a better way.

So the input data comes in the form of a string of 81 numeric characters among 0..9. The string should fill up the board line by line. So if my board supposed to be a 2D list, then a chunksOf 9 . map ((read :: String-&gt; Int) . pure) would be sufficient. However the Board type is a 4 fold nested Axis type.

To start with, I couldn't even find a way to write a Read instance for the Board type at all. My solution is to first convert the flat list into a nested list of proper structure like.

nestList :: [a] -&gt; [[[[a]]]]
nestList = map transpose . chunksOf 3 . chunksOf 3 . chunksOf 3


and then after a 2 days war with GHC infinite type errors and whatnot, i could finally come up with this stinky part.

axify :: [[[[a]]]] -&gt; Board a
axify ([a,b,c]) = Axis (Axis (axify' $ a !! 0) (axify' $ a !! 1) (axify' $ a !! 2))
                       (Axis (axify' $ b !! 0) (axify' $ b !! 1) (axify' $ b !! 2))
                       (Axis (axify' $ c !! 0) (axify' $ c !! 1) (axify' $ c !! 2))
                  where
                  axify' ([a,b,c]) = Axis (Axis (a !! 0) (a !! 1) (a !! 2))
                                          (Axis (b !! 0) (b !! 1) (b !! 2))
                                          (Axis (c !! 0) (c !! 1) (c !! 2))


Now this works. I can fill the Board properly from a flat string and the Show instance just gives back a stringified version of the nestedList.

Question

Could anybody please help me with a proper Read instance or at least an axify function that is idiomatic (generalized to n fold Axis a type)?

Many thanks in advance.
Consider the polymorphic function:

axis :: [a] -&gt; Axis a
axis [x,y,z] = Axis x y z


You want to apply this at each of the four "list levels" of the output [[[[a]]]] of nestList.  So, if you have:

lst4 : [[[[Char]]]]
lst4 = nestList "295743861431865927876192543387459216612387495549216738763534189928671354154938672"


you want to write:

axis lst4 :: Axis [[[Char]]]


to replace the outermost list with an Axis, then you want to fmap axis over the Axis:

fmap axis . axis $ lst4 :: Axis (Axis [[Char]])


to replace the second-level list with an Axis, then you want to do a double-fmap (fmap axis):

fmap (fmap axis) . fmap axis . axis $ lst4 :: Axis (Axis (Axis [Char]))


to replace the third-level list, and finally the innermost list:

fmap (fmap (fmap axis)) . fmap (fmap axis) . fmap axis . axis $ lst4 :: Axis (Axis (Axis (Axis Char)))


So, you actually have:

axify :: [[[[a]]]] -&gt; Board a
axify = fmap (fmap (fmap axis)) . fmap (fmap axis) . fmap axis . axis


Alternatively, instead of using the functor instance for Axis, you could use the functor instance for lists, starting from the inside out:

axify = axis . map axis . map (map axis) . map (map (map axis))