2

I am writing a program in Haskell which repeatedly takes its most recent result and uses this to compute the next result. I want to be able to see the newest result in response to user input, so I tried something like this:

main = mainhelper 0

mainhelper count = do
 count <- return (count + 1)
 line <- getLine
 if null line
  then do mainhelper count
  else do
   putStrLn $ show count
   return ()

I was hoping that getLine would return an empty line if the user hasn't entered anything, but this doesn't happen, instead the program does nothing until it receives user input. Is there a way around this?

3
  • 1
    It is not entirely clear what you expect from getLine. Do you want be non-blocking? That is, always return immediately whether the user has typed anything or not? Commented Sep 1, 2016 at 15:49
  • Yes, I wanted it to return empty line immediately if the user hasn't typed anything. Commented Sep 1, 2016 at 16:03
  • This is not how standard input/output works in any language. Haskell is no exception. If it worked like you want it would be rather impossible to write a program that interacts with a user in the normal fashion (get some input, process it, print output). Commented Sep 1, 2016 at 16:13

2 Answers 2

6

One simple solution is to fork a thread for the complicated computation and communicate with the main UI thread via MVar. For example:

import Control.Exception
import Control.Monad
import Control.Concurrent

thinkReallyHard x = do
    threadDelay 1000000 -- as a proxy for something that's actually difficult
    evaluate (x+1)

main = do
    v <- newMVar 0
    forkIO (forever (modifyMVar_ v thinkReallyHard))
    forever (getLine >> readMVar v >>= print)

You may wonder about the role of evaluate in thinkReallyHard. The subtlety there is that MVars are lazy -- they can contain thunks just as easily as computed values. In particular, this means it's easy to accidentally push all the pure computation from the forked thread into the thread that's reading and using the contents of the MVar. The call to evaluate simply forces the forked thread to finish the pure computation before writing to the MVar.

Sign up to request clarification or add additional context in comments.

2 Comments

I guess it's worth noting that when the user requests the current value in this version, it waits until the next iteration finishes before printing. It's not hard to change, but slightly less modular, and not difficult once you have the idea to use multiple threads.
Thank you! This works as hoped, now time for me to go read up on the relevant modules.
0

It does return an empty line if you hit enter without entering text -- you just immediately prompt for more input, so it might look like nothing is happening. But if you run the program, hit enter three times, then enter something non-empty, you'll see that the final count reflects the multiple entries.

Here's a modified version of your code that does the same thing, but is slightly more canonical:

main = mainhelper 0

mainhelper count = do
  let count' = count + 1
  line <- getLine
  if null line
    then mainhelper count'
    else print count'

Rather than count <- return (count + 1), you can write let count' = count + 1 -- this is a pure binding, not something that needs to invoke the IO monad (as you're doing with <- and return). But I used count' instead of count because otherwise that will create a recursive binding. The '-suffixing is a standard idiom for a "modified version" of an identifier.

Next I switched putStrLn . show to print, which is part of the Prelude and does exactly that.

I got rid of the return () because print (and putStrLn) already have the type IO (). This allows you to elide the do statements, as there's now a single IO expression in each branch of the if.


It's not really clear what you're trying to do here that's different from what you are doing -- the code does (in imperative terms) increment a counter every time the user presses enter, and displays the state of the counter every time the user enters some non-empty text.

Here's another version that prints the counter every time, but only increments it when prompted, which may or may not be helpful to you:

main = mainhelper 0

mainhelper count = do
  print count
  line <- getLine
  mainhelper (if null line then count else succ count)

I'm using succ, the successor function, instead of the explicit + 1, which is just a style preference.

1 Comment

Sorry I wasn't clear, in this case I want my program to enter a loop which increments a counter every time it runs and I want my loop to run whether or not I provide input. I want my program to display the current count when I give it an input. So I expect an output along the lines of 10^7 after a few moments, not 1.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.