views:

339

answers:

4

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?

+2  A: 

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.

Dave Hinton
Like I said, this is contrived. The pointless 'where' clause was bolted on because this is what I was testing and curious about. I was naively trying to follow up on a stereotyped idea of good functional form by trying to avoid imperative-looking 'do' blocks wherever I thought I could and by doing weird things the type system isn't intended to accomodate. But this is clearly the more sensible way. Your suggestion is correct and is working for me. Thank you.
cikkle
+2  A: 

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
sth
No, no. I understand the IO monad is one-way, the articles and books I've gone through beat that into my head early on. I worried that was a little ambiguous so I went back after posting to add the type signature to rndPermu to clarify I meant for it to remain in IO, although I thought GHCi would infer that anyhow. I'm not interested in ripping tainted values out of IO; I know that would be awful even if it were possible. It's specifically whether or not it's possible to pattern match against a tuple in the IO monad I was confused over.
cikkle
> ... I went back after posting to add the type signature to rndPermu to clarify I meant for it to remain in IO, although I thought GHCi would infer that anyhow. Oh, but it did infer the type. It just didn't infer what your intentions were. Also, as others have expounded already, you can tuple-match perfectly well against a tuple in the IO monad (or any monad); you just can't do it in a way that implies that you're pulling the value apart frorm its "monadic home".
BMeph
+1  A: 

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)
Alexey Romanov
A: 

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.

Dan
This may or may not make any sense, but my original thinking wasn't that I could necessarily coerce types in such a strongly-typed language, but rather that some kind of polymorphism would come into play when pattern matching, whereby tuples in monads are just subsets of tuples in general and there would be no kind of conflict in that statement at all. Otherwise I was trying to find some syntactically valid equivalent of: " where IO (front, back) = hurf xs " in the hope the pattern itself could somehow be cast into the IO monad. A little strange, I know.
cikkle
But that's just it. A tuple in a monad is not a subset of tuples in general. It's a subset of monads in general. But it's not necessarily a subset of IO monads exclusively. You could use a similar construct with any monad. The reason `IO (front, back) = hurf xs` doesn't work is because `IO` is not a simple data constructor. There might be potentially be other datatypes where that would work, but not IO.
Dan
Just for added emphasis. I think I now see what the confusion with the other replies was. When you write `IO (front, back) = hurf xs` you may not be intending to "break out" of the IO monad, but to a Haskell programmer, that's exactly what it looks like you're trying to do.
Dan