1

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
Will Ness
  • 70,110
  • 9
  • 98
  • 181
Mittenchops
  • 18,633
  • 33
  • 128
  • 246

2 Answers2

1

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"
chi
  • 111,837
  • 3
  • 133
  • 218
  • Thank you, and this is helpful, but when I change the return type of the lambda to be the same as the x that comes in as \x as I do in the update 2 and just do the print as incidental rather than the return, I still get the same error. I understand your for_ loop is correct, but why does my revised fmap not work like the function I am replacing up top? – Mittenchops Apr 25 '19 at 16:53
  • @Mittenchops `fmap` on a list produces a list. Your original code did not involve any lists. Try asking GHCi about the type of your expressions, you will see that they are of type `[IO ()]` instead of `IO something`, so you need `sequence_` if you want to actually run the list of IO actions. – chi Apr 25 '19 at 18:05
1

You wrote:

but when I change the return type of the lambda to be the same as the x that comes in as \x as I do in the update 2 ...

No, no. You don't. A lambda function returns the value of its last expression. Your lambda function has just one expression in it -- the entire do { ... } block defines a value, which is that lambda function's return value. Not x. The return belongs to the do, not the lambda expression. It is easier to see if we write it with the explicit separators, as

fmap (\x -> do {
              let m = T.pack $ show x ;
              T.putStrLn $ m ;
              return x
              } ) [1..10]

The do block as a whole has the same monadic type as each of its line statements.

One of those is putStrLn ..., whose type is IO (). So your lambda function returns IO t for some t.

And because of return x, t is the type of x. We have return :: Monad m => t -> m t, so with m ~ IO it is return :: t -> IO t.

x comes from the argument list Num t => [t], so overall you have

Num t => fmap (fx :: t -> IO t) (xs :: [t]) :: [IO t]

or

           xs :: [t]
        fx    ::  t  ->  IO t
   ----------------------------
   fmap fx xs ::        [IO t]
Will Ness
  • 70,110
  • 9
  • 98
  • 181