Search code examples
haskellmonadsio-monaddo-notation

fmap into a do block fails with a print error


I'm trying to understand why a function I have written with a do-block can't be rewritten to fmap a similar lambda expression over a list.

I have the following:

-- This works
test1 x = do 
        let m = T.pack $ show x
        T.putStrLn m

test1 1

Produces

1

But

-- This fails
fmap (\x -> do 
              let m = T.pack $ show x
              T.putStrLn m
              ) [1..10]

-- And this also fails
fmap (\x -> do 
             T.putStrLn $ T.pack $ show x
                ) [1..10]

With error:

<interactive>:1:1: error:
    • No instance for (Show (IO ())) arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it

My putStrLn is consistent between the working and the non-working. The imports are the same. My show-pack-putstrln dance required to print is also consistent between the working and the non-working.

What is happening that the use of print is changing between the working and non-working?

Update 1

-- I was also surprised that this fails
fmap (T.putStrLn $ T.pack $ show) [1..10]
-- it seemed as similar as possible to the test1 function but mapped.

<interactive>:1:7: error:
    • Couldn't match expected type ‘Integer -> b’ with actual type ‘IO ()’
    • In the first argument of ‘fmap’, namely ‘(T.putStrLn $ pack $ show)’
      In the expression: fmap (T.putStrLn $ pack $ show) [1 .. 10]
      In an equation for ‘it’: it = fmap (T.putStrLn $ pack $ show) [1 .. 10]
    • Relevant bindings include it :: [b] (bound at <interactive>:1:1)
<interactive>:1:29: error:
    • Couldn't match type ‘() -> String’ with ‘String’
      Expected type: String
        Actual type: () -> String
    • Probable cause: ‘show’ is applied to too few arguments
      In the second argument of ‘($)’, namely ‘show’
      In the second argument of ‘($)’, namely ‘pack $ show’
      In the first argument of ‘fmap’, namely ‘(T.putStrLn $ pack $ show)’

Update 2

-- This lambda returns x of the same type as \x
-- even while incidentally printing along the way
fmap (\x -> do 
              let m = T.pack $ show x
              T.putStrLn $ m
              return x
              ) [1..10]

But also fails with:

<interactive>:1:1: error:
    • No instance for (Show (IO Integer)) arising from a use of ‘print’
    • In a stmt of an interactive GHCi command: print it

Solution

  • The type of fmap f [1..10] is [T] where T is the return type of f.

    In your case, T = IO (), so the type of the full expression is [IO ()].

    IO actions can not be printed, so GHCi complains when you try to print that list. You might want to run those actions instead of printing them, using something like sequence_ (fmap f [1..10]).

    Alternatively, consider ditching fmap and instead using something like

    import Data.Foldable (for_)
    
    main = do
       putStrLn "hello"
       for_ [1..10] $ \i -> do
          putStrLn "in the loop"
          print (i*2)
       putStrLn "out of the loop"