【问题标题】:How to change Tab-completed content at runtime in Haskeline?如何在 Haskeline 运行时更改 Tab 完成的内容?
【发布时间】:2021-03-29 18:29:58
【问题描述】:

我想写一个文本界面,它提供了一些默认的命令。该程序支持这些命令的制表符补全。

该程序还记录用户输入并将其存储在StateData 中。现在我希望这个程序支持这些用户输入的选项卡完成。例如:

*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 之类的不安全机制的情况下做到这一点?有没有办法将更新后的stlooprepl)传递到replSettings startStaterepl)?

我是 Haskeline 的新手,感谢您抽出宝贵时间。

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 = ...

【问题讨论】:

    标签: haskell haskeline


    【解决方案1】:

    IORef 不是不安全的;你已经在IO,所以在这里添加可变状态是一种非常合理的方式。

    但是如果你想避免IO,你可以简单地使用StateT StateData IO作为InputT的底层monad,从而在Settings中完成函数。看来你已经在尝试使用StateT。这是一个完整的示例,它只是将每个条目添加到列表中并天真地自动完成它们:

    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 / modifyReaderT (IORef StateData) IO / readIORef / modifyIORef 没有什么不同。

    事实上,如果你把IORef in StateData 放在你的代码中,假设它是一个更复杂的记录类型在你的代码中,后者是使它的某些部分可变的好方法,并且其他不可变。

    data StateData = StateData
      { mutableThing   :: !(IORef Thing)
      , immutableStuff :: !Stuff
      …
      }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-12-05
      • 1970-01-01
      • 2013-03-06
      • 2016-04-06
      • 2015-04-25
      • 1970-01-01
      • 2011-08-03
      相关资源
      最近更新 更多