Search code examples
haskellstate-monad

adding state monad to the haskell kv database


Playing with an old code review here simple-db. and get stuck when I added State Monad into it to further reduce the code.

I somehow feel I am not doing it correctly. repl cmd = getCmd >>= execCmd cmd >>= displayResult >>= continue change the interface of execCmd means change all the functions along the line.

if I follow this approach, how to get it work?

what's the correct way of doing the suggested refactoring using state Monad ?

--import qualified
import Data.Map as M
import Control.Monad.State

type History = [String]

data Result = Result (Maybe String) Bool

data Command = Invalid | End | Get String | Set String String
data DB = DB (M.Map String String)


execCmd :: Command -> State DB Result
execCmd (Set key val) = do
  (db@(DB map), r) <- get
  let newMap = M.insert key val map
  put ((DB newMap), r)
  return $ Result Nothing False 
execCmd (Get key) = do
  (db@(DB map), r) <- get
  return $ Result (M.lookup key map) False 
execCmd End = do
  return $ Result Nothing True 
execCmd Invalid = do
  return $ Result Nothing False 

getCmd = getLine >>= return . parseCmd

parseCmd :: String -> Command
parseCmd s =
  case words s of
    ("set":key:value:_) -> Set key value
    ("get":key:_)       -> Get key
    ("end":_)           -> End
    _                   -> Invalid

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

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

repl cmd = getCmd >>= execCmd cmd >>= displayResult >>= continue

startState = ((DB M.empty), (Result Nothing False))

--main = repl Invalid 

Solution

  • For starters, your code doesn't type check, so let's just concentrate on the first part of execCmd:

    execCmd :: Command -> State DB Result
    execCmd (Set key val) = do
      (db@(DB map), r) <- get
      let newMap = M.insert key val map
      put ((DB newMap), r)
      return $ Result Nothing False
    

    Clearly this isn't going to work because get and put are operating with a pair (DB, r) but your type signature says that the state type is only a DB.

    What is r here? Do you need it? This definition of execCmd agrees with the type signature:

    execCmd :: Command -> State DB Result
    execCmd (Set key val) = do
      db@(DB map) <- get
      let newMap = M.insert key val map
      put (DB newMap)
      return $ Result Nothing False