I love having an excuse to do concurrent programming in Haskell. Along with refactoring, it is one of its superpowers.
A common pattern in interactive programming is having a slow consumer (input processing) and a fast producer (user input).
Imagine you are making…oh I don’t know…a hotdog recognizer.
Sure, I know what you are thinking — there already plenty of those — but ours will be different: it will be terminal based.
If a user is holding ‘h’ then after the analyzer runs, it should classify the input as a “terminal hotdog”. If the user is holding down anything else it should classify it as “not a terminal hotdog”.
The problem is that our classifier is not done yet, so we are going to make it appear like it is working for a demo (it will sleep for 4 seconds). We don’t want to queue up old classification results (key requirement). Instead, the app should attempt show the classification result for the most recent input.
We need a bounded queue of size 1. A single element queue is another way to view an
The consumer thread that does the classification will read from the
MVar if there is a new element, and will block if the
import Control.Monad consumer :: MVar Char -> IO ()
consumer var = forever $ do
-- Read the most recent character.
-- Block and wait for a character if the MVar is empty.
x <- takeMVar var putStrLn "\nanalyzing"
-- Sleep for 4 secs to trick our investors
-- TODO actual use tensorflow for real here with this
if x == 'h' then
putStrLn "It's a terminal hotdog"
putStrLn "It's not a terminal hotdog"
The producer will wait for input from the user. When it gets a character, it will clear the
MVar and insert the most recent value.
producer :: MVar Char -> IO ()
producer var = forever $ do
-- Wait for a user to input a character
input <- getChar
-- Empty the MVar if there is older input waiting for processing.
_ <- tryTakeMVar var
-- Insert the new into the MVar so the consumer can process it.
putMVar var input
Tie it Together
main function, we create a new empty
MVar and pass it new a thread for the consumer. We call the producer on the main thread.
main :: IO ()
main = do
var <- newEmptyMVar
forkIO $ consumer var
The great thing about this is, it works, it’s easy and this patterns shows up a lot, so it’s worth learning.