Ndosho Mbwana
Ndosho Mbwana

Reputation: 31

How does `return` statement have different type than that of a function's definition?

Within a loop, integers are collected inside a list, and a tuple of these integers is returned. How does this change to a list of tuples?

input :: IO [(Int,Int)]
input = do
  n <- readLn :: IO Int
  forM [1..n] $ \_ -> do
    [x,y] <- map read . words <$> getLine
    return (x,y)

I expected type of value to be (Int,Int) but it is [(Int,Int)]. Why?

Upvotes: 0

Views: 684

Answers (1)

Will Ness
Will Ness

Reputation: 71075

Let's re-write your code with explicit separators making the code structure more self-apparent:

input :: IO [(Int,Int)]
input = do {
    n <- readLn ;
    forM [1..n] (\ _ -> do {
        [x,y] <- fmap (map read . words) getLine ;
        return (x,y)  })
    }

so return (x,y) belongs to the internal do.

Since there's a getLine :: IO String there, the internal do's type is IO (t1,t2) where x :: t1, y :: t2. So this is also the return type of that lambda function participating in that forM call.

Since forM :: Monad m => [a] -> (a -> m b) -> m [b], and we know m ~ IO here, we get the type of the overall do's last expression as

forM :: [a] -> (a -> IO b) -> IO [b]

and thus the overall type is IO [b] ~ IO [(t1,t2)] since b ~ (t1,t2) as per that return expression.

The lambda function returns IO b so forM returns IO [b] as per its type above. And the type of do block is the same as the type of its last expression.

The function's signature says it's IO [(Int,Int)], so finally t1 ~ Int and t2 ~ Int and everything fits.

Upvotes: 3

Related Questions