问题
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