I've got one of those algorithms which "threatens to march off the right edge"
(in the words of Goerzen et al). I need something like a State or Maybe monad,
but this is inside the IO monad. So I presume I need StateT or MaybeT. However,
I'm still (slowly) learning about monads from first principles. I thought I
might present my code and get some pointers... maybe someone could actually show
me how to rewrite it, which would be a neat way to see MaybeT and StateT in
action. I'm hoping to get anything from a one-line response to a rewrite of my
code. Anything will help.
Here's the program:
{-
This is a program which starts with a document containing "notes"
about software requirements (in a particular format) and puts them
into a database. Notes include details such as the "source" of the
requirement (who gave it), the topic(s) to which it pertains, the
date, etc.
I have written a parser to take a text document typed up by me during a
meeting and parse it into a NoteRecord structure. Here is the
structure:
-}
data NoteRecord = NoteRecord {
recordSource :: String, -- Name of person who gave req.
recordDate :: [Int], -- Date in [<year>,<month>,<date>]
recordSourceType :: String, -- "meeting", "phone", "email", etc.
recordBugNum :: Maybe Int, -- Bugzilla # (if relevant)
recordTopics :: [String], -- list of official topics pertaining
recordText :: String } -- the text of the note itself
deriving (Show)
{-
One other wrinkle. The source (person name) and topic must be one
of a set of pre-determined strings. A person has an official full name
which is stored in the database. Topics also have official descriptive
strings. If I wasn't clever, then the note, as I type it up,
must have the exact name and topic. But I hate trying to remember things
like that. So I have implemented a "fuzzy string match" system so
that I can type part of someone's name (or even misspell it) or part of
a topic string, and the system will find the best match to an official
string.
In pseudocode, the function to insert a note in the database must do this:
This function starts with a NoteRecord.
- If text already exists in the database, give an error and skip to end.
- Fuzzy-match strings to topics and source.
- If no potential match can be found to some of topics or source,
give error and skip to end.
- Ask user to confirm if the matched topics and source look okay.
- if user says no, skip to end.
- Actually insert the record.
-}
insertNote :: NoteRecord -> Connection -> IO ()
insertNote nr conn =
do -- Check if it exists in the database already.
status <- checkPreExistingText nr conn
if status
then putStrLn "Skipping... text exists already."
else
do -- Find best fit for all topics and source.
-- See type signatures below.
bestFitTopics <- fitTopics nr conn
bestFitSource <- fitSource nr conn
case any isNothing bestFitTopics of
True ->
putStrLn "Error... some topic couldn't be matched."
False ->
case bestFitSource of
Nothing ->
putStrLn "Error.. source couldn't be matched."
_ ->
do b <- isUserOkay nr bestFitTopics bestFitSource
if b
then do
-- Create a new NoteRecord with matched
-- and validated topics/source.
nrValidated =
nr { recordTopics = bestFitTopics
, recordSource = bestFitSource }
insertRow nrValidated conn
else putStrLn "Abort due to user request."
checkPreExistingText :: NoteRecord -> Connection -> Bool
-- There are multiple topics in the NoteRecord. For each one,
-- find the best fuzzy match, or indicate if there is no plausible
-- match at all.
fitTopics :: NoteRecord -> Connection -> [Maybe String]
-- There is one source. Try to find fuzzy match.
fitSource :: NoteRecord -> Connection -> Maybe String
-- Present user with all fuzzy matches and get a yes/no response if it's
-- okay to proceed.
isUserOkay :: NoteRecord -> [Maybe String] -> Maybe String -> Bool
-- Do actual insert into database.
insertRow :: NoteRecord -> Connection -> IO ()