[Newbie question] -- Looping stdin until condition is met

Allright, this is a definitely a newbie question. I'm learning Haskell and going through the exercises in the beautiful Hutton book, and one of them requires for me to write a loop that queries a line from the user (stdin), looping until the user enters a valid integer (at least that's how I want to implement the interface to the exercise). I have tried tons of variants and modifications of code, and I can't find the way to implement this. Here is what my emacs buffer is at now:: import Text.Read import System.IO import qualified Control.Exception as C getNum :: IO Int getNum = do line <- (hGetLine stdin) x <- (C.catch (do return (read line :: Int)) (\e -> getNum)) return x main = do x <- getNum putStr ((show x) ++ "\n") Now, I've tried the Prelude's catch, the Control.Exception catch, I've tried moving it at the top of getnum, I've tried without catch, I've tried a ton of other shtuff, so much that I'm starting to think that Emacs is going to run out of electrons soon. I asked some half-newbie friends who are insanely enthousiastic about Haskell and they can't do it either (I'm starting to think that those enthousiastic friends are dating a beautiful girl with 3 PhDs, but she has a 2-inch thick green and gooey wart smack on her nose and they're so blindly in love that they can't admit that she does). I've asked some university profs and they sidetrack my question by saying I shouldn't do I/O so early. Can anyone here restore my faith in the Haskell section of humanity? 1. How do I catch the exception that is raised from "read"? 2. Where do I find the appropriate information I need in order to fix this? I'm probably just not searching in the right place. (Yes, I've seen the GHC docs, and it doesn't help, maybe I'm missing some background info.) 3. Please do not tell me I should solve the problem differently. Here is the problem I'm trying to solve, and nothing else: "Write a program that reads a line from the user, looping the query until the line contains a valid integer." It shouldn't be too hard i think. The best answer would be a two-liner code example that'll make me feel even more stupid than I already do. Thanks in advance.

On Fri, May 30, 2008 at 5:28 PM, Martin Blais
Allright, this is a definitely a newbie question.
I'm learning Haskell and going through the exercises in the beautiful Hutton book, and one of them requires for me to write a loop that queries a line from the user (stdin), looping until the user enters a valid integer (at least that's how I want to implement the interface to the exercise). I have tried tons of variants and modifications of code, and I can't find the way to implement this. Here is what my emacs buffer is at now::
import Text.Read import System.IO import qualified Control.Exception as C
getNum :: IO Int getNum = do line <- (hGetLine stdin) x <- (C.catch (do return (read line :: Int)) (\e -> getNum)) return x
main = do x <- getNum putStr ((show x) ++ "\n")
Now, I've tried the Prelude's catch, the Control.Exception catch, I've tried moving it at the top of getnum, I've tried without catch, I've tried a ton of other shtuff, so much that I'm starting to think that Emacs is going to run out of electrons soon. I asked some half-newbie friends who are insanely enthousiastic about Haskell and they can't do it either (I'm starting to think that those enthousiastic friends are dating a beautiful girl with 3 PhDs, but she has a 2-inch thick green and gooey wart smack on her nose and they're so blindly in love that they can't admit that she does). I've asked some university profs and they sidetrack my question by saying I shouldn't do I/O so early. Can anyone here restore my faith in the Haskell section of humanity?
1. How do I catch the exception that is raised from "read"?
I think you want readIO, which yields a computation in the IO monad, so it can be caught.
2. Where do I find the appropriate information I need in order to fix this? I'm probably just not searching in the right place. (Yes, I've seen the GHC docs, and it doesn't help, maybe I'm missing some background info.)
In this particular case, I am not sure where you'd find this information. It's not very intuitive to a beginner why "read" doesn't work in this case.
3. Please do not tell me I should solve the problem differently. Here is the problem I'm trying to solve, and nothing else:
"Write a program that reads a line from the user, looping the query until the line contains a valid integer."
It shouldn't be too hard i think. The best answer would be a two-liner code example that'll make me feel even more stupid than I already do.
Thanks in advance.
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

philip.weaver:
1. How do I catch the exception that is raised from "read"?
I think you want readIO, which yields a computation in the IO monad, so it can be caught.
Ah, that's a third option, sequence the effect using readIO, import System.IO import qualified Control.Exception as C main = do x <- getNum print x getNum :: IO Integer getNum = do y <- maybeRead case y of Nothing -> getNum Just n -> return n maybeRead :: Read a => IO (Maybe a) maybeRead = C.catch (do x <- getLine n <- readIO x return (Just n)) -- ensure any exception is thrown here (const (return Nothing)) -- Don

On Fri, 30 May 2008 16:54:18 -0700, "Philip Weaver"
1. How do I catch the exception that is raised from "read"?
I think you want readIO, which yields a computation in the IO monad, so it can be caught.
Holy schmoly, there it is, words of wisdom, written as clearly as can be, from the docs: The readIO function is similar to read except that it signals parse failure to the IO monad instead of terminating the program. I'll be prosternating on the floor towards my web browser for the next four hours. Thank you very much Philip. (And thank you Don for the verbose examples.)
2. Where do I find the appropriate information I need in order to fix this? I'm probably just not searching in the right place. (Yes, I've seen the GHC docs, and it doesn't help, maybe I'm missing some background info.)
In this particular case, I am not sure where you'd find this information. It's not very intuitive to a beginner why "read" doesn't work in this case.
Dear Philip, could you point your virtual finger towards a reference/paper/book/any-bleeping-thing that would help this simple beginner understand why it doesn't work in this case? I'm trying to picture why a "read" function that terminates the program would be useful anywhere. In fact, any library function other than something like UNIX's "exit" or "kill" which terminates my program is not really welcome in any of my computer programs, but then again, I haven't yet been illuminated by the genie of pure functional languages. A reference would be awesome.

On Fri, May 30, 2008 at 5:14 PM, Martin Blais
On Fri, 30 May 2008 16:54:18 -0700, "Philip Weaver"
said: 1. How do I catch the exception that is raised from "read"?
I think you want readIO, which yields a computation in the IO monad, so it can be caught.
Holy schmoly, there it is, words of wisdom, written as clearly as can be, from the docs:
The readIO function is similar to read except that it signals parse failure to the IO monad instead of terminating the program.
I'll be prosternating on the floor towards my web browser for the next four hours. Thank you very much Philip.
(And thank you Don for the verbose examples.)
2. Where do I find the appropriate information I need in order to fix this? I'm probably just not searching in the right place. (Yes, I've seen the GHC docs, and it doesn't help, maybe I'm missing some background info.)
In this particular case, I am not sure where you'd find this information. It's not very intuitive to a beginner why "read" doesn't work in this case.
Dear Philip, could you point your virtual finger towards a reference/paper/book/any-bleeping-thing that would help this simple beginner understand why it doesn't work in this case? I'm trying to picture why a "read" function that terminates the program would be useful anywhere. In fact, any library function other than something like UNIX's "exit" or "kill" which terminates my program is not really welcome in any of my computer programs, but then again, I haven't yet been illuminated by the genie of pure functional languages. A reference would be awesome.
Sorry, I wouldn't know where to point you, other than stating the simple rule that you can't catch exceptions in pure code. Others may be able to enlighten you better. By the way, the example that Dons gave may look more verbose, but (when possible) it's a probably a better idea to capture failure in a Maybe than in IO. I gave you "readIO" because it fit in to the exception handling that you were trying to do, and because you said you didn't want anyone to tell you you were doing things wrong :).

On Fri, 30 May 2008 17:19:54 -0700, "Philip Weaver"
Dear Philip, could you point your virtual finger towards a reference/paper/book/any-bleeping-thing that would help this simple beginner understand why it doesn't work in this case? I'm trying to picture why a "read" function that terminates the program would be useful anywhere. In fact, any library function other than something like UNIX's "exit" or "kill" which terminates my program is not really welcome in any of my computer programs, but then again, I haven't yet been illuminated by the genie of pure functional languages. A reference would be awesome.
Sorry, I wouldn't know where to point you, other than stating the simple rule that you can't catch exceptions in pure code. Others may be able to enlighten you better.
By the way, the example that Dons gave may look more verbose, but (when possible) it's a probably a better idea to capture failure in a Maybe than in IO. I gave you "readIO" because it fit in to the exception handling that you were trying to do, and because you said you didn't want anyone to tell you you were doing things wrong :).
Here is a private reply from another user, which is more explanatory, the problem was that the read function wasn't getting called at a point where it could have been caught (I'll have to look into that into more detail): All you need is a little strictness, x <- (C.catch (return $! read line :: Int) (\e -> getNum)) works. Another option is using evaluate instead of return. The problem is that (read line :: Int) is not evaluated until it is needed, that is when it's going to be printed, but then it's too late to catch the exception. Some general remarks: hGetLine stdin === getLine do x <- action return x is the same as action

On 5/30/08, Martin Blais
Dear Philip, could you point your virtual finger towards a reference/paper/book/any-bleeping-thing that would help this simple beginner understand why it doesn't work in this case? I'm trying to picture why a "read" function that terminates the program would be useful anywhere. In fact, any library function other than something like UNIX's "exit" or "kill" which terminates my program is not really welcome in any of my computer programs, but then again, I haven't yet been illuminated by the genie of pure functional languages. A reference would be awesome.
As others have pointed out, the reason why it doesn't work is due to lazy evaluation; the exception is "hidden" in a pure value and isn't triggered until some other bit of code causes the pure value to be evaluated. You can force the exception to be generated in your catch block by using "seq" (or "$!", which is implemented using "seq"). This forces the evaluation to happen at that point, although there are a lot of gotchas involved; for Int it works fine, though. Your question, then, requires asking "why would you want to hide an exception in a pure value"? The answer to that is quite interesting, but here's a simpler example that might enlighten you: head :: [a] -> a head (x:_) = x head _ = error "head: empty list" Just as you are required to prove before calling "head" that you aren't passing an empty list, for your program to be total, you should prove before calling "read" that the string parses properly. If you can't provide that proof (because, in this case, the string is provided by the user), you should be using another function for parsing. Don suggested "reads" to implement "maybeRead", which seems like a great idea to me. -- ryan

blais:
Allright, this is a definitely a newbie question.
I'm learning Haskell and going through the exercises in the beautiful Hutton book, and one of them requires for me to write a loop that queries a line from the user (stdin), looping until the user enters a valid integer (at least that's how I want to implement the interface to the exercise).
1. How do I catch the exception that is raised from "read"?
The best thing to do is bypass read and use 'reads' to define your own safe read. maybeRead :: Read a => String -> Maybe a maybeRead s = case reads s of [(x, "")] -> Just x _ -> Nothing For example, yielding: import System.IO main = do x <- getNum print x getNum :: IO Integer getNum = do n <- getLine case maybeRead n of Nothing -> getNum Just n -> return n
2. Where do I find the appropriate information I need in order to fix this? I'm probably just not searching in the right place. (Yes, I've seen the GHC docs, and it doesn't help, maybe I'm missing some background info.)
I think Control.Exception.catch should be fine here.
3. Please do not tell me I should solve the problem differently. Here is the problem I'm trying to solve, and nothing else:
"Write a program that reads a line from the user, looping the query until the line contains a valid integer."
It shouldn't be too hard i think. The best answer would be a two-liner code example that'll make me feel even more stupid than I already do.
Of course, it's easy. You can have fun now abstracting out the loop form in getNum using say, MaybeT or friends. But a loop is simple and easy. If you want to write it with explict exception handling of the read parse failure, it's more tedious, as you need to ensure the read exception is thrown within the body of the enclosing catch. For example, import System.IO import qualified Control.Exception as C main = do x <- getNum print x getNum :: IO Integer getNum = do y <- maybeRead case y of Nothing -> getNum Just n -> return n maybeRead :: Read a => IO (Maybe a) maybeRead = C.catch (do x <- getLine let n = read x n `seq` return (Just n)) -- ensure any exception is thrown here (const (return Nothing)) -- Don

Hi
The best thing to do is bypass read and use 'reads' to define your own safe read.
maybeRead :: Read a => String -> Maybe a maybeRead s = case reads s of [(x, "")] -> Just x _ -> Nothing
Or just use the Safe package: http://hackage.haskell.org/cgi-bin/hackage-scripts/package/safe http://hackage.haskell.org/packages/archive/safe/0.2/doc/html/Safe.html#v%3A... Thanks Neil
participants (5)
-
Don Stewart
-
Martin Blais
-
Neil Mitchell
-
Philip Weaver
-
Ryan Ingram