Keep reading user keyboard input if available, while echoing back the latest available On StackOverflow I have asked a question a few minutes ago, but then by trying a bit more, I found the answer myself, and I'm posting the resulting working code here for review.
What the code does, is waiting for, and repeatedly prompting the user to enter a character; when the user enters one, the program echoes it back and goes back to the previous mode, waiting for further input. The program assumes an initial implicit input of 'A'.
{-# language LambdaCase #-}

import Control.Concurrent
import Data.Maybe
import System.IO

main :: IO ()
main = do
  hSetBuffering stdin NoBuffering
  future_input &lt;- newEmptyMVar
  work future_input 'l'

work :: MVar Char -&gt; Char -&gt; IO ()
work future_input latest_input = do
  forkIO $ putMVar future_input =&lt;&lt; getChar
  wait future_input latest_input
    where
      wait :: MVar Char -&gt; Char -&gt; IO ()
      wait future_input latest_input =
        tryTakeMVar future_input &gt;&gt;=
          \case Just input -&gt; putStrLn (&quot;new input &quot; ++ return input)
                              &gt;&gt; work future_input input
                Nothing    -&gt; putStrLn (&quot;old input &quot; ++ return latest_input)
                              &gt;&gt; threadDelay 100000
                              &gt;&gt; wait future_input latest_input

Some thoughts on about the code:

I've tried to use applicative/monadic style wherever I could, without compromising readability too much;
as regards the concurrentcy-handling part, basically the wait function, I preferred using the do sugar because I could hardly get something working this way.
With a bit of suggestion/help on Haskell's IRC channels, I came up with this, which doesn't forkIO at every recursion.
{-# language LambdaCase #-}

import Control.Concurrent
import Data.Maybe
import System.IO

main :: IO ()
main = do
  hSetBuffering stdin NoBuffering
  future_input &lt;- newEmptyMVar
  forkIO $ pollInput future_input
  processInput future_input 'l'

pollInput :: MVar Char -&gt; IO ()
pollInput future_input = (putMVar future_input =&lt;&lt; getChar) &gt;&gt; pollInput future_input

processInput :: MVar Char -&gt; Char -&gt; IO ()
processInput future_input latest_input = tryTakeMVar future_input &gt;&gt;=
    \case Just input -&gt; putStrLn (&quot;direction change &quot; ++ return input)
                        &gt;&gt; processInput future_input input
          Nothing    -&gt; putStrLn (&quot;keep direction &quot; ++ return latest_input)
                        &gt;&gt; threadDelay 100000
                        &gt;&gt; processInput future_input latest_input