cikkle
cikkle

Reputation: 458

Pattern matching against a tuple in the IO Monad in Haskell

I've been studying Haskell in my spare time and have recently crossed into the area of monadic functions. I've distilled the code from an excercise I've been working on into this very contrived example to isolate the exact problem I'm having:

import System.Random

rndPermu :: [a] -> IO (a, [a])
rndPermu xs = (front, back)
    where (front, back) = hurf xs

hurf :: [a] -> IO (a, [a])
hurf xs = randomRIO (0, (length xs) - 1) >>= \r -> return $ removeAt r xs

removeAt :: Int -> [a] -> (a, [a])
removeAt n xs = (e, rest)
    where e    = xs !! n
          rest = take n xs ++ (tail $ drop n xs)

rndPermu produces a type error when loaded into GHCi stating a type (t, t1) was expected in the 'where' clause but IO (a, [a]) was received. I can use things like (liftM fst) to pull individual items from the tuple and just assign a single value but that's obviously a sloppy and roundabout way of going about things. I feel I'm probably stumbling over some minor nuance of syntax that's staring me in the face. How does one resolve this type error? It should be possible to directly match against a tuple wrapped in a monad, shouldn't it?

Upvotes: 3

Views: 4946

Answers (4)

Dan
Dan

Reputation: 11069

To answer the question from your comment, GHCi does infer that rndPermu should have an IO type. That's not the problem. The problem is this line:

 where (front, back) = hurf xs

Type inference just means (loosely) that you don't have to specify the types of the expressions you're dealing with. Type inference does not mean that Haskell will simply convert values of one type to another silently; quite the opposite in fact. As others mentioned, you don't have to write a do block if you don't want to, but you do have to deal with the fact that you have an IO value.

Upvotes: 0

Alexey Romanov
Alexey Romanov

Reputation: 170723

As an alternative to a do block, you can pattern match in the function to which you bind the monadic value:

rndPermu xs = hurf xs >>= \(front, back) -> return (front, back)

rndPermu xs = hurf xs >>= \res -> case res of (front, back) -> return (front, back)

Upvotes: 1

sth
sth

Reputation: 229593

If I understand correctly what you are trying to do, rndPermu tries to take the value in IOreturned by hurf and remove the IO from it, like rndPermu :: IO a -> a. This is not possible. A return value in the IO monad signals that the hurf function uses IO and all functions that use the results of a call to hurf will therefore indirectly also use IO: Their return values should also be in the IO monad. This is enforced by the type system.

If you just want to use pattern matching in a monad the most direct way is to use the do-notation:

rndPermu :: [a] -> IO (a, [a])
rndPermu xs =
   do (front, back) <- hurf xs
      return (front, back)

A common pattern is to use different, pure functions to do further processing of values. These functions are just called from IO or a different monad, but they don't need to know about that:

-- pure function to do something with the result of |hurf|
modify :: (a, [a]) -> (a, [a])
modify (a, as) = (a, reverse as)

rndPermu :: [a] -> IO (a, [a])
rndPermu xs =
   do r <- hurf xs
      return (modify r)
-- or, with >>= operator:
-- hurf xs >>= return . modify

Upvotes: 2

dave4420
dave4420

Reputation: 47052

I don't know why you don't have

rndPermu xs = hurf xs

but to answer the question you asked, try this

rndPermu xs = do (front, back) <- hurf xs
                 return (front, back)

My understanding is that you cannot directly match something within IO. You have to extract it first using the <- syntax.

Upvotes: 2

Related Questions