I want to write a text interface, which provides some default commands. This program supports tab completion of those commands.
This program also records user inputs and stores it in StateData
. And now I want this program to support tab completion of those user inputs. For example:
*Main > main
> read a<tab> -- press tab and no suggestions (read is a default command)
> read abcde
...
> read a<tab> -- press tab
abcde -- suggestions
Is it possible to do that without using unsafe mechanism like IORef
? Is there a way to pass updated st
from loop
(in repl
) to replSettings startState
(in repl
)?
I am new to Haskeline and thanks for your time.
repl :: StateData -> IO()
repl startState = runInputT (replSettings startState) $ loop startState
where
loop :: StateData -> InputT IO ()
loop st = do
inputL <- getInputLine "> "
case inputL of
Nothing -> return ()
Just "quit" -> outputStrLn "--Exited--" >> return ()
Just ipt -> do (opt, st') <- process ipt `runStateT` st
...
loop st'
replSettings :: StateData -> Settings IO
replSettings st =
Settings
{ complete = replCompletion st,
historyFile = Just "history.txt",
autoAddHistory = True
}
replCompletion :: StateData -> CompletionFunc IO
replCompletion st = completeWordWithPrev Nothing [' '] st (\x y -> return $ completionGenerator x y)
completionGenerator :: String -> String -> StateData -> [Completion]
completionGenerator "" c st =
commandSuggestion c (updatesSuggestions st) -- I wish to update it at run time
completionGenerator p c st = ...
IORef
isn’t unsafe; you’re already inIO
, so it’s a perfectly reasonable way to add mutable state here.But if you want to avoid
IO
, you can simply useStateT StateData IO
as the underlying monad forInputT
, and thus the completion function inSettings
. It seems you’re already trying to useStateT
anyway. Here’s a complete example that just adds every entry to a list and autocompletes them naïvely:The completion generator could also be written using
MonadState
(frommtl
) to insulate it from being able to accessIO
, and other code could likewise use this pure state while being agnostic toIO
. But otherwise, since you’re already inIO
in this code,StateT StateData IO
/get
/modify
are no different thanReaderT (IORef StateData) IO
/readIORef
/modifyIORef
.In fact, if you put an
IORef
inStateData
, supposing it’s a more complex record type in your code, the latter is a good way to make some parts of it mutable and others immutable.