Reputation: 3392
While I can apply a function two times and bind result in a tuple:
let foo :: Num a => a -> a
foo x = x + 1
let (x,y) = (foo 10, foo 20)
This can't be done (ar at least I don't know how to do it properly) within a do
block:
let bar :: Num a => a -> IO a
bar x = do
let y = x + 1
return y
let test :: Num a => IO a
test = do
(x,y) <- (bar 10, bar 20)
return y
I got the following error when typing it GHCI REPL:
:29:15:
Couldn't match expected type ‘IO a1’ with actual type ‘(t0, a)’
Relevant bindings include
test :: IO a (bound at :28:5)
In the pattern: (x, y)
In a stmt of a 'do' block: (x, y) <- (bar 10, bar 20)
In the expression:
do { (x, y) <- (bar 10, bar 20);
return y }
:29:24:
Couldn't match type ‘(,) (IO a0)’ with ‘IO’
Expected type: IO (IO a1)
Actual type: (IO a0, IO a1)
In a stmt of a 'do' block: (x, y) <- (bar 10, bar 20)
In the expression:
do { (x, y) <- (bar 10, bar 20);
return y }
In an equation for ‘test’:
test
= do { (x, y) <- (bar 10, bar 20);
return y }
I can obviously solve it with a more verbose:
let test' :: Num a => IO a
test' = do
x <- bar 10
y <- bar 20
return y
Is there a correct way to express test
without making it like test'
?
Upvotes: 1
Views: 389
Reputation: 2361
I will take the freedom to suggest a few changes to your code. Here is my version:
import Control.Monad
-- no need for the do and let
bar :: Num a => a -> IO a
bar x = return $ x + 1 -- or: bar = return . (1+)
-- liftM2 to make (,) work on IO values
test :: Num a => IO a
test = do (x,y) <- liftM2 (,) (bar 10) (bar 20) -- or: (,) <$> bar 10 <*> bar 20
return y
-- show that this actually works
main :: IO ()
main = test >>= print
Your types did not match: your (bar 10, bar 20)
evaluates to type Num a => (IO a, IO a)
but you're treating it as Num a => IO (a, a)
. By lifting (,)
we're making it work on IO
values, and returning an IO
value.
Look at this (GHCi, import Control.Monad
to get liftM2
):
:t (,)
-- type is :: a -> b -> (a, b)
:t liftM2 (,)
-- type is :: Monad m => m a -> m b -> m (a, b)
in our case the Monad
is the IO
monad. Thus, the final output of liftM2 (,)
will work nicely inside the IO
do-block because it returns a proper IO
value.
And, of course you could solve this particular problem with the less verbose:
test'' = bar 20
PS: Please don't return stuff into the IO
monad without any reason. You're making perfectly pure operations look unpure and there's no reasonable way back.
Upvotes: 6
Reputation: 120741
import Control.Applicative
test = do (x,y) <- (,) <$> bar 10 <*> bar 20
return y
Aka (x,y) <- liftA2(,) (bar 10) (bar 20)
.
Of course, for this specific example (where x
is just thrown away) it would be equivalent and much better to just write
test = bar 20
Upvotes: 7
Reputation: 15959
you will need a helper function to lift the IO
from inside the tuple
for brevity's sake I will use Int
instead of Num a => a
(bar 1, bar 2) :: (IO Int, IO Int)
thus you need something with signature
liftTuple :: (IO x, IO y) -> IO (x, y)
liftTuple (mx, my) = ...
then you can do the (x,y) <- liftTuple (bar 1, bar 2)
Upvotes: 1