问题
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
回答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"
回答2:
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]
来源:https://stackoverflow.com/questions/55853955/fmap-into-a-do-block-fails-with-a-print-error