Search code examples
haskellfunctional-programmingiofold

How to foldl an io operation on a list generated by a zip in order to print 2 columns?


I have seen several questions regard to use of foldl and IO but none of them seem to provide a solution to my case.

I am simply trying to output two columns of numbers.

0 256
1 256
2 256
...
256 256
0 255
1 255
2 255
3 255
4 255

I have tried the following but I was only able to print a single line and not an entire column:

zipMerge :: [a] -> [b] -> [(a,b)]
zipMerge [] _      = []
zipMerge _  []     = error "second list cannot be empty"
zipMerge (a:as) bs = (zip (replicate (length bs) a) bs) ++ (zipMerge as bs)

-- foldl :: (a -> b -> a) -> a -> [b] -> a
printPixel :: IO() -> (Int, Int) -> IO()
printPixel _ (i, j) = putStrLn (show i) ++ " " ++ (show j)

printPixels :: [(Int, Int)] -> IO()
printPixels colors = foldl printPixel (return ()) colors

printGradient :: IO()
printGradient = do
    putStrLn "255"
    let is = [0..256]
        js = [256,255..0]
    printPixels (zipMerge js is)

What am I doing wrong ?


Solution

  • A fold is definitely overkill here. The idea of fold is to keep some sort of state as you traverse the list, but in your case there is no state to keep: you just need to perform an effect for every element in order, and independently of other elements.

    To do this, use mapM_:

    printPixel :: (Int, Int) -> IO()
    printPixel (i, j) = putStrLn $ (show i) ++ " " ++ (show j)
    
    printPixels :: [(Int, Int)] -> IO()
    printPixels colors = mapM_ printPixel colors
    

    Or its twin for_, which is the same thing, but with arguments in reverse order (and weaker constraints), which lets you provide the effect as a lambda-expression, but without parentheses, making it look almost like the for constructs in C-like languages:

    printPixels :: [(Int, Int)] -> IO()
    printPixels colors = for_ colors $ \(i, j) -> putStrLn $ (show i) ++ " " ++ (show j)