Search code examples
haskelliopretty-print

Pretty-Printing 2D List in Haskell (Explanation of Failure)


Answers exist for how to pretty-print in Haskell, including how to do it with packages, but this is more a question about why my first approach failed.

Given a 2D list of the form w = [r,r1,r2], where w,r are of respective data types w::W, r::R (simple vars for the example, not my actual naming convention):

data X = Y | Z
         deriving (Eq, Show)

data R = [X]
         deriving (Eq, Show)

data W = [R]
         deriving (Eq, Show) 

Why wouldn't either of the following approaches work? (I'm fully aware this will include the brackets and commas, that's not an issue)

ppR :: R -> IO ()
ppR = putStrLn . show

and either

ppW :: W -> IO W (?)
ppW (w:[]) = ppR w
ppW (w:ws) = ppR w >> ppW ws

or

ppW :: W -> IO W (?)
ppW (w:[]) = putStrLn . show w
ppW (w:ws) = putStrLn . show w >> ppW ws

I don't quite know the types of ppW, I'm still learning. In addition to my clearly naive (lack of) understand of Haskell's handling of impure functions, I'm also stymied by the fact that the second approach to ppW typechecks (with no provided type).

Why doesn't this approach work? Can you not recursively print a 2D list of indeterminate length in Haskell?


Solution

  • In your case the type should be ppW :: W -> IO (). () (pronounced "unit") is a type which can have only one* value - namely (). So IO () is an IO action which as a result gives no value (It is equivalence of void functions from C, C++ or Java).

    Here is some piece of code that should compile:

    data X = Y | Z
      deriving (Eq, Show)
    
    type R = [X]
    
    type W = [R]
    
    ppR :: R -> IO ()
    ppR = putStrLn . show
    
    ppW :: W -> IO ()
    ppW (w:[]) = ppR w
    ppW (w:ws) = ppR w >> ppW ws
    

    If you have any problem with types you can always use ghci to determine type of any function. Or you can simply look at the types of functions you are using:

    putStrLn :: String -> IO()
    (>>) :: IO a -> IO b -> IO b -- this is not exactly what ghci would tell you.
    

    Edit: In the code I included I used type instead of data. type defines a type synonym (it is like C/C++ typedef). It is easier to use them than to use data - data requires you to write explicitly the name of constructor, so you would have:

    data R =  R [X]
    
    ppR :: R -> IO ()
    ppR (R r)= putStrLn (show r)
    

    *actually it can also be also bottom, but that's a different story.