gsscoder
gsscoder

Reputation: 3392

Store result of function applications in a tuple inside a DO-block

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

Answers (3)

Sam van Herwaarden
Sam van Herwaarden

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

leftaroundabout
leftaroundabout

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

epsilonhalbe
epsilonhalbe

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

Related Questions