# Solution for Gophercises Quiz game in Haskell

Here is my solution for the Quiz game exercise from https://gophercises.com/. The goal is to write a program that will read questions and their correct answers from a CSV file, ask them from a user one by one, and print the number of correct answers. The interesting part is that a user has a limited amount of time for answering questions and the quiz should stop as soon as the time limit has exceeded.

I use MissingH and parsec to parse CSV files, optparse-applicative to parse CLI options.

From a code review, I expect some general advice and answers to the following specific questions:

• Is there a way to rewrite play function without MVar's?
• How to separate quiz logic from the concurrent implementation (forking, MVar manipulation) in the play function?
• The first version of the solution was without a timer and to count the number of correct answers I used the following function:

Code for kinda beautiful count function:

count :: [IO Bool] -> IO Integer
count [] = return 0
count (act:xs) = do
correct <- act
fmap ((if correct then 1 else 0) +) (count xs)


With the presence of a timer, I have to use another MVar (which is an ugly solution IMHO) to hold the number of correct answers because the thread could be killed at any time. Is it possible to stop recursion somehow when the thread got killed to return the partial result?

Code for the final solution:

module Main where

import Lib

import System.IO
import Text.ParserCombinators.Parsec (parseFromFile)
import Data.CSV (csvFile)
import Control.Applicative (liftA2)
import Options.Applicative
import Data.Semigroup ((<>))
import Control.Concurrent.MVar
import Control.Exception

data Options = Options {inputFile :: String, time :: Int}
options :: Parser Options
options = Options
<$> strOption (long "csv-file" <> value "problems.csv") <*> option auto (long "time" <> value 30) main :: IO () main = do Options {inputFile = file, time = t} <- execParser$ info options briefDesc
result <- parseFromFile csvFile file
case result of
Left e -> putStrLn ("CSV parse error: " ++ show e)
Right lines -> play lines t

play :: [[String]] -> Int -> IO ()
play lines time = do
putStrLn $"Total number of questions is " ++ (show . length) lines waitAnyKey done <- newEmptyMVar correct <- newMVar 0 questionsT <- forkIO$ do
putMVar done True
timerT <- forkIO $do threadDelay$ time * 10^6
putStrLn "\nTimes is out"
putMVar done True
takeMVar done

c <- takeMVar correct
putStrLn $"\nNumber of correct answers is " ++ show c waitAnyKey :: IO () waitAnyKey = do hSetBuffering stdin NoBuffering putStrLn "Hit any key when you get ready" getChar putChar '\b' hSetBuffering stdin LineBuffering askQuestions :: [IO Bool] -> MVar Bool -> MVar Integer -> IO () askQuestions [] done correct = putMVar done True askQuestions (act:xs) done correct = do c <- act when c$ modifyMVar_ correct \$ \ v -> return (v + 1)

• A basic recommendation is to use the async library to synchronize threads instead of explicit MVars. – Li-yao Xia Sep 11 '20 at 6:28