问题描述
我想编写一个文本界面,其中提供了一些默认命令.该程序支持这些命令的制表符完成.
I want to write a text interface, which provides some default commands. This program supports tab completion of those commands.
该程序还记录用户输入并将其存储在 StateData
中.现在,我希望该程序支持这些用户输入的制表符完成.例如:
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
是否可以在不使用 IORef
之类的不安全机制的情况下做到这一点?有没有一种方法可以将更新的 st
从 loop
(在 repl
中)传递到 replSettings startState
(在中)repl
)?
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
)?
我是Haskeline的新手,感谢您的宝贵时间.
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
并非不安全;您已经在 IO
中,因此这是在此处添加可变状态的一种非常合理的方法.
IORef
isn’t unsafe; you’re already in IO
, so it’s a perfectly reasonable way to add mutable state here.
但是,如果要避免使用 IO
,则可以简单地将 StateT StateData IO
用作 InputT
的基础单子,从而完成 Settings
中的功能.看来您已经尝试使用 StateT
.这是一个完整的示例,该示例仅将每个条目添加到列表中,然后自动将其自动完成:
But if you want to avoid IO
, you can simply use StateT StateData IO
as the underlying monad for InputT
, and thus the completion function in Settings
. It seems you’re already trying to use StateT
anyway. Here’s a complete example that just adds every entry to a list and autocompletes them naïvely:
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State (StateT, evalStateT, get, modify)
import Data.List (isPrefixOf)
import System.Console.Haskeline
type StateData = [String]
main :: IO ()
main = repl []
repl :: StateData -> IO ()
repl startState
= flip evalStateT startState
$ runInputT replSettings loop
where
loop :: InputT (StateT StateData IO) ()
loop = do
inputL <- getInputLine "> "
case inputL of
Nothing -> pure ()
Just "quit" -> outputStrLn "--Exited--"
Just ipt -> do
-- Just add each entry to the state directly.
lift $ modify (ipt :)
loop
replSettings :: Settings (StateT StateData IO)
replSettings = Settings
{ complete = replCompletion
, historyFile = Just "history.txt"
, autoAddHistory = True
}
replCompletion :: CompletionFunc (StateT StateData IO)
replCompletion = completeWordWithPrev Nothing " " completionGenerator
completionGenerator :: String -> String -> StateT StateData IO [Completion]
completionGenerator prefix suffix = do
st <- get
-- Trivial completion that just ignores the suffix.
pure $ fmap (\ s -> Completion s s True)
$ filter (prefix `isPrefixOf`) st
也可以使用 MonadState
(来自 mtl
)编写完成生成器,以使其无法访问 IO
和其他代码同样可以使用这种纯状态,而与 IO
无关.但是否则,因为您已经在此代码的 IO
中,所以 StateT StateData IO
/ get
/ modify
与 ReaderT(IORef StateData)IO
/ readIORef
/ modifyIORef
没什么不同.
The completion generator could also be written using MonadState
(from mtl
) to insulate it from being able to access IO
, and other code could likewise use this pure state while being agnostic to IO
. But otherwise, since you’re already in IO
in this code, StateT StateData IO
/ get
/ modify
are no different than ReaderT (IORef StateData) IO
/ readIORef
/ modifyIORef
.
实际上,如果您在代码中放置了 IORef
StateData
,则假定它是一种更复杂的记录类型,则后者是使它的某些部分可变而其他部分不可变的好方法.
In fact, if you put an IORef
in StateData
, 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.
data StateData = StateData
{ mutableThing :: !(IORef Thing)
, immutableStuff :: !Stuff
…
}
这篇关于如何在Haskeline的运行时更改制表符完成的内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!