HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Very simple in-memory database

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
databasesimplememoryvery

Problem

I've implemented a very simple in-memory "database" just as an exercise. I wanted to see if there's anything obvious I should be doing that fits more of the Haskell way of doing things.

import qualified Data.Map as M

type History = [String]

data Result = Result (Maybe String) Bool DB

data Command = Command { name  :: String
                       , key   :: Maybe String
                       , value :: Maybe String
                       }

data DB = DB (M.Map String String)

cmdKey c   = let Just s = key   c in s
cmdValue c = let Just s = value c in s

runSetCmd :: Command -> DB -> IO (Maybe String, DB)
runSetCmd c db@(DB map) = do
    let newMap = M.insert (cmdKey c) (cmdValue c) map
    return $ (Nothing, DB newMap)

runGetCmd :: Command -> DB -> IO (Maybe String, DB)
runGetCmd c db@(DB map) = return $ (M.lookup (cmdKey c) map, db)

execCmd :: DB -> Command -> IO Result
execCmd db c@(Command name key value) = do
    (output,newDB)  runSetCmd c db
        "get" -> runGetCmd c db
        _     -> return (Nothing, db)
    return $ Result output end newDB

  where
    end = case name of
              "end" -> True
              _     -> False

getCmd = getLine >>= return . parseCmd

parseCmd :: String -> Command
parseCmd s =
    case words s of
        (name:key:value:_) -> Command name (Just key) (Just value)
        (name:key:[])      -> Command name (Just key) Nothing
        (name:[])          -> Command name Nothing Nothing

displayResult :: Result -> IO Result
displayResult r@(Result (Just s) _ _) = putStrLn s >> return r
displayResult r                       = return r

continue :: Result -> IO ()
continue (Result _ end db) = if end then return () else repl db

repl state = getCmd >>= execCmd state >>= displayResult >>= continue

main = repl (DB M.empty)

Solution

First of all, as Karl said, you should minimize the code in IO monad and make sure most of your code is in pure functions

Second your Command type is not properly designed

data Command = Command { name  :: String
                         , key   :: Maybe String
                         , value :: Maybe String
                       }


Having the name as String is not cool. You could have a type something like

data CommandType = Get | Set


...but still your command type has too many invalid combinations which the type system cannot help you with (for example: name="get", key="xyz", value="asdfasdf"). Your type should be designed such that you avoid these combinations and you make the type system work for you.

Given this I would define the Command type like this:

data Command = Invalid | End | Get String | Set String String


if your type is like this then execCmd will become:

exec (Get key) = ....
exec (Set key value) = ...
exec Exit = ...
exec Invalid = ...


If you only address these two issues you will see a lot of simplification in your code. Other things would be:

Using the State monad will help you with the passing of the current db state between the functions operating on it. If you do this then passing the state will not be your concern and your result could be either a tuple or a record:

data Result = Result { output: String ; end:Bool}


Then your displayResult will look like this:

UPDATE

If you don't want the a command to output anything then the output field in Result could have the type "Maybe String" instead of String. \

data Result = Result { output: Maybe String ; end:Bool}


Then you will have to choose only the Just outputs before you display the results.

Code Snippets

data Command = Command { name  :: String
                         , key   :: Maybe String
                         , value :: Maybe String
                       }
data CommandType = Get | Set
data Command = Invalid | End | Get String | Set String String
exec (Get key) = ....
exec (Set key value) = ...
exec Exit = ...
exec Invalid = ...
data Result = Result { output: String ; end:Bool}

Context

StackExchange Code Review Q#24741, answer score: 5

Revisions (0)

No revisions yet.