Paul
Paul

Reputation: 382

Losing IO on return type

I am having trouble with the function consensus in this block of code. The recursive definition of consensus is returning [Action] instead of IO [Action].

I am new to Haskell and do not understand why this is happening. I was under the impression that it was not possible remove the IO from a return value.

import System.Random (randomRIO)
import Data.Ord (comparing)
import Data.List (group, sort, maximumBy)

data Action = A | B deriving (Show, Eq, Ord)

-- Sometimes returns a random action
semiRandomAction :: Bool -> Action -> IO (Action)
semiRandomAction True a = return a
semiRandomAction False _ = do
  x <- randomRIO (0, 1) :: IO Int
  return $ if x == 0 then A else B

-- Creates a sublist for each a_i in ls where sublist i does not contain a_i
oneOutSublists :: [a] -> [[a]]
oneOutSublists [] = []
oneOutSublists (x:xs) = xs : map (x : ) (oneOutSublists xs)

-- Returns the most common element in a list
mostCommon :: (Ord a) => [a] -> a
mostCommon = head . maximumBy (comparing length) . group . sort

-- The function in question
consensus :: [Bool] -> [Action] -> IO [Action]
consensus [x] [action] = sequence [semiRandomAction x action]
consensus xs actions = do
  let xs' = oneOutSublists xs
      actions' = map (replicate $ length xs') actions
  replies <- mapM (uncurry $ consensus) (zip xs' actions')
  map mostCommon replies -- < The problem line

main = do
  let xs = [True, False, False]
      actions = [A, A, A]
  result <- consensus xs actions
  print result

ghc output

➜  ~ stack ghc example.hs 
[1 of 1] Compiling Main             ( example.hs, example.o )

example.hs:29:3: error:
    • Couldn't match type ‘[]’ with ‘IO’
      Expected type: IO [Action]
        Actual type: [Action]
    • In a stmt of a 'do' block: map mostCommon replies
      In the expression:
        do let xs' = oneOutSublists xs
               actions' = map (replicate $ length xs') actions
           replies <- mapM (uncurry $ consensus) (zip xs' actions')
           map mostCommon replies
      In an equation for ‘consensus’:
          consensus xs actions
            = do let xs' = ...
                     ....
                 replies <- mapM (uncurry $ consensus) (zip xs' actions')
                 map mostCommon replies
   |
29 |   map mostCommon replies
   |   

Upvotes: 2

Views: 103

Answers (2)

DevNewb
DevNewb

Reputation: 861

I think what you're looking for is

return $ map mostCommon replies

return is a standard function that wraps values into monad.

You can think of it this way:

  • you're inside an IO monad, so GHC expects that your function will return IO a
  • you're returning [] Action (it's just another way of writing [Action])

so there are two errors here:

  • your actual return type does not match your type annotation ([Action] vs IO [Action])
  • the types wrapping the Action ([] and expected IO) do not match

When you use return function here you fix both errors.

Upvotes: 4

melpomene
melpomene

Reputation: 85767

consensus is supposed to return a value of type IO [Action]. That's what Expected type: IO [Action] means.

However, map mostCommon replies is an expression of type [Action] (because map returns a plain list, no IO).

I was under the impression that it was not possible remove the IO from a return value.

Indeed, which is why you're getting a type error. The "cannot remove IO" thing is not a fundamental property, it just follows from the types of available operations in the standard library.

So how do we solve this?

You have a value of type IO [[Action]], namely mapM (uncurry $ consensus) (zip xs' actions'). You want to apply mostCommon to each inner list (within the outer list within IO).

By using <- in a do block, you can locally "extract" values from IO a:

replies <- mapM (uncurry $ consensus) (zip xs' actions')
-- replies :: [[Action]]

By using map, you can apply mostCommon to each sublist:

map mostCommon replies :: [Action]

What's missing is that you need to "rewrap" your value in IO to make the do block pass type-checking (each individual statement in a do block must have the same base type, in this case IO):

return (map mostCommon replies)

Here return :: [Action] -> IO [Action] (or in general: return :: (Monad m) => a -> m a).

Upvotes: 5

Related Questions