使用动态值

时间:2018-04-02 19:37:30

标签: haskell autocomplete

我在Haskell中有以下程序从命令行获取输入并修改mydata变量的状态:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}

import Text.Regex.PCRE
import System.Console.Haskeline
import System.IO
import System.IO.Unsafe
import Control.Monad.State.Strict
import qualified Data.ByteString.Char8 as B
import Data.Maybe
import Data.List 
import qualified Data.Map as M

data MyDataState = MyDataState {
  mydata :: [Int],
  showEven :: Bool
} deriving (Show)

myfile :: FilePath
myfile = "data.txt"

defaultFlagValue :: Bool
defaultFlagValue = False

saveDataToFile :: [Int] -> IO ()
saveDataToFile _data = withFile myfile WriteMode $ \h -> hPutStr h (unwords $ map show _data)

{-# NOINLINE loadDataFromFile #-} 
loadDataFromFile :: [Int]
loadDataFromFile = map read . words $ B.unpack $ unsafePerformIO $ B.readFile myfile

wordList = [":help", ":q", ":commands", ":show", ":save", ":edit", ":new", ":toggleShowEven"]

searchFunc :: String -> [Completion]
searchFunc str = map simpleCompletion $ filter (str `isPrefixOf`) (wordList)

mySettings :: Settings (StateT MyDataState IO)
mySettings = Settings { historyFile = Just "myhist"
                      , complete = completeWord Nothing " \t" $ return . searchFunc
                      , autoAddHistory = True
                      }

help :: InputT (StateT MyDataState IO) ()
help = liftIO $ mapM_ putStrLn
       [ ""
       , ":help     - this help"
       , ":q        - quit"
       , ":commands - list available commands"
       , ""
       ]

commands :: InputT (StateT MyDataState IO) ()
commands = liftIO $ mapM_ putStrLn
       [ ""
       , ":show           - display data"
       , ":save           - save results to file"
       , ":edit           - edit data"
       , ":new            - generate new element "
       , ":toggleShowEven - toggle display of even elements"
       , ""
       ]

toggleFlag :: InputT (StateT MyDataState IO) ()
toggleFlag = do
  MyDataState mydata flag <- get
  put $ MyDataState mydata (not flag)

instance MonadState s m => MonadState s (InputT m) where
    get = lift get
    put = lift . put
    state = lift . state

parseInput :: String -> InputT (StateT MyDataState IO) () 
parseInput inp
  | inp =~ "^\\:q"        = return ()

  | inp =~ "^\\:he"       = help >> mainLoop

  | inp =~ "^\\:commands" = commands >> mainLoop

  | inp =~ "^\\:toggleShowEven" = toggleFlag >> mainLoop

  | inp =~ "^\\:show" = do
      MyDataState mydata showEven <- get
      liftIO $ putStrLn $ unwords $ if showEven 
        then map show mydata
        else map show $ filter odd mydata
      mainLoop 

  | inp =~ "^\\:save" = do
      MyDataState mydata _ <- get 
      liftIO $ saveDataToFile mydata
      mainLoop

  | inp =~ "^\\:load" = do
      put (MyDataState loadDataFromFile defaultFlagValue)
      mainLoop

  | inp =~ "^\\:new" = do
      MyDataState mydata showEven <- get                     -- reads the state
      inputData <- getInputLine "\tEnter data: "
      case inputData of 
        Nothing -> put ( MyDataState [0] showEven )
        Just inputD -> 
          put $ if null mydata 
            then MyDataState [read inputD] showEven
            else MyDataState (mydata ++ [read inputD]) showEven -- updates the state
      mainLoop

  | inp =~ ":" = do
    outputStrLn $ "\nNo command \"" ++ inp ++ "\"\n"
    mainLoop

  | otherwise = handleInput inp

handleInput :: String -> InputT (StateT MyDataState IO) ()
handleInput inp = mainLoop

mainLoop :: InputT (StateT MyDataState IO ) ()
mainLoop = do
  inp <- getInputLine "% "
  maybe (return ()) parseInput inp

greet :: IO ()
greet = mapM_ putStrLn
        [ ""
        , "          MyProgram"
        , "=============================="
        , "For help type \":help\""
        , ""
        ]

main :: IO ((), MyDataState)
main = do 
    greet 
    runStateT (runInputT mySettings mainLoop) MyDataState {mydata = [] , showEven = defaultFlagValue}

与上述程序互动的示例:

*Main> main

          MyProgram
==============================
For help type ":help"

% :commands 

:show           - display data
:save           - save results to file
:edit           - edit data
:new            - generate new element 
:toggleShowEven - toggle display of even elements

% :show

% :new
    Enter data: 1
% :new 
    Enter data: 2
% :new 
    Enter data: 3
% :show
1 3
% :toggleShowEven 
% :show
1 2 3
% 

您可能已经注意到,此程序正在使用命令行自动完成功能来执行:show:edit:new等典型命令。

我的问题如下。是否可以使用wordsList中的值扩展可用于自动完成(MyDataState变量)的命令列表?例如,如果mydata包含值1, 2, 3,我希望它与可用于自动完成的命令一起显示 - 当键入 Tab 时,我将得到以下命令列表,而不是仅通过wordsList静态定义: :help :q {{ 1}} :commands :show :save :edit :new :toggleShowEven :1 :2 即可。如何扩展:3定义以包含searchFunc中定义的值?有可能吗?

1 个答案:

答案 0 :(得分:3)

Settings记录中,字段complete的类型为CompletionFunc (StateT MyDataState IO),这意味着我们可以访问状态以进行自动完成。

目前mySettings的定义使用

complete = completeWord Nothing " \t" $ return . searchFunc

这个return包装了一个纯函数,从而忽略了有状态的上下文。我们可以用访问状态的计算来替换它:

complete = completeWord Nothing " \t" $ \str -> do
  _data <- get
  return (searchFunc _data str)

还将searchFunc的类型更改为:

searchFunc :: MyDataState -> String -> [Completion]