【问题标题】:combining StateT with InputT将 StateT 与 Input 相结合
【发布时间】:2016-06-20 19:19:14
【问题描述】:

这是this question 的后续。我正在尝试将@ErikR 的answer 中的shell 合并到我的InputT 循环中。

main :: IO [String]
main = do
    c <- makeCounter
    execStateT (repl c) []

repl :: Counter -> StateT [String] IO ()
repl c = lift $ runInputT defaultSettings loop
  where
  loop = do
    minput <- getLineIO $ in_ps1 $ c
    case minput of
      Nothing -> lift $ outputStrLn "Goodbye."
      Just input -> (liftIO $ process c input) >> loop

getLineIO :: (MonadException m) => IO String -> InputT m (Maybe String)
getLineIO ios = do
    s <- liftIO ios
    getInputLine s

得到一个错误

Main.hs:59:10:
    Couldn't match type ‘InputT m0’ with ‘IO’
    Expected type: StateT [String] IO ()
      Actual type: StateT [String] (InputT m0) ()
    Relevant bindings include
      loop :: InputT (InputT m0) () (bound at Main.hs:61:3)
    In the expression: lift $ runInputT defaultSettings loop
    In an equation for ‘repl’:
        repl c
          = lift $ runInputT defaultSettings loop
          where
              loop
                = do { minput <- getLineIO $ in_ps1 $ c;
                       .... }

Main.hs:62:5:
No instance for (Monad m0) arising from a do statement
The type variable ‘m0’ is ambiguous
Relevant bindings include
  loop :: InputT (InputT m0) () (bound at Main.hs:61:3)
Note: there are several potential instances:
  instance Monad (Text.Parsec.Prim.ParsecT s u m)
    -- Defined in ‘Text.Parsec.Prim’
  instance Monad (Either e) -- Defined in ‘Data.Either’
  instance Monad Data.Proxy.Proxy -- Defined in ‘Data.Proxy’
  ...plus 15 others
In a stmt of a 'do' block: minput <- getLineIO $ in_ps1 $ c
In the expression:
  do { minput <- getLineIO $ in_ps1 $ c;
       case minput of {
         Nothing -> lift $ outputStrLn "Goodbye."
         Just input -> (liftIO $ process c input) >> loop } }
In an equation for ‘loop’:
    loop
      = do { minput <- getLineIO $ in_ps1 $ c;
             case minput of {
               Nothing -> lift $ outputStrLn "Goodbye."
               Just input -> (liftIO $ process c input) >> loop } }

完整代码可以在here找到,它基于Write you a haskell

我知道haskelline 内置了对历史记录的支持,但我正在尝试自己实现它作为练习。

随意建议替换 monad 转换器以获得相同的功能。

我真正的问题

我想在 Write You a Haskell 中为 lambda REPL 添加类似 ipython 的功能,即:

我。输入和输出的计数器,将出现在提示符中,即

In[1]>
Out[1]>

这已经是done

二。将每个命令保存到历史记录(自动),并使用特殊命令显示所有先前的命令,例如histInput(与ipython 中的hist 相同)。此外,保存所有输出结果的历史记录并使用histOutput 显示它们。这就是我在这个问题中尝试做的事情(暂时只输入历史记录)。

三。参考以前的输入和输出,例如如果In[1]x,则In[1] + 2 应替换为x + 2,同样用于输出。

更新

我尝试结合@ErikR 的answer,并暂时禁用showStep,想出:

module Main where

import Syntax
import Parser
import Eval
import Pretty
import Counter

import Control.Monad
import Control.Monad.Trans
import System.Console.Haskeline
import Control.Monad.State

showStep :: (Int, Expr) -> IO ()
showStep (d, x) = putStrLn ((replicate d ' ') ++ "=> " ++ ppexpr x)

process :: Counter -> String -> InputT (StateT [String] IO) ()
process c line =
    if ((length line) > 0)
       then
        if (head line) /= '%'
            then do
                modify (++ [line])
                let res = parseExpr line
                case res of
                    Left err -> outputStrLn $ show err
                    Right ex -> do
                        let (out, ~steps) = runEval ex
                        --mapM_ showStep steps
                        out_ps1 c $ out2iout $ show out
        else do
                let iout = handle_cmd line
                out_ps1 c iout

    -- TODO: don't increment counter for empty lines
    else do
      outputStrLn ""

out2iout :: String -> IO String
out2iout s = return s

out_ps1 :: Counter -> IO String -> InputT (StateT [String] IO) ()
out_ps1 c iout = do
      out <- liftIO iout
      let out_count = c 0
      outputStrLn $ "Out[" ++ (show out_count) ++ "]: " ++ out
      outputStrLn ""

handle_cmd :: String -> IO String
handle_cmd line = if line == "%hist"
                     then
                        evalStateT getHist []
                     else
                         return "unknown cmd"

getHist :: StateT [String] IO String
getHist = do
    hist <- lift get
    forM_ (zip [(1::Int)..] hist) $ \(i, h) -> do
                                show i ++ ": " ++ show h

main :: IO ()
main = do
    c <- makeCounter
    repl c

repl :: Counter -> IO ()
repl c = evalStateT (runInputT defaultSettings(loop c)) []

loop :: Counter -> InputT (StateT [String] IO) ()
loop c = do
    minput <- getLineIO $ in_ps1 $ c
    case minput of
      Nothing -> return ()
      Just input -> process c input >> loop c

getLineIO :: (MonadException m) => IO String -> InputT m (Maybe String)
getLineIO ios = do
    s <- liftIO ios
    getInputLine s

in_ps1 :: Counter -> IO String
in_ps1 c = do
    let ion = c 1
    n <- ion
    let s = "Untyped: In[" ++ (show n) ++ "]> "
    return s

仍然无法编译:

Main.hs:59:5:
    Couldn't match type ‘[]’ with ‘StateT [String] IO’
    Expected type: StateT [String] IO String
      Actual type: [()]
    In a stmt of a 'do' block:
      forM_ (zip [(1 :: Int) .. ] hist)
      $ \ (i, h) -> do { show i ++ ": " ++ show h }
    In the expression:
      do { hist <- lift get;
           forM_ (zip [(1 :: Int) .. ] hist) $ \ (i, h) -> do { ... } }
    In an equation for ‘getHist’:
        getHist
          = do { hist <- lift get;
                 forM_ (zip [(1 :: Int) .. ] hist) $ \ (i, h) -> ... }

【问题讨论】:

  • haskeline 已经为你实现了命令行历史——看看usage example
  • 谢谢,我知道,但我想自己实现它作为练习。
  • 如果你想自己实现history,为什么InputT会出现在你的代码中?
  • 取自 Stephen Diehl 在 WYAH 中的原始代码。我本身不需要InputT,我只需要获取一个带有自定义提示的输入行,从而使用getInputLine,其类型为(MonadException m) =&gt; String -&gt; InputT m (Maybe String)

标签: haskell monads monad-transformers state-monad


【解决方案1】:

第一个错误是因为你声明了

main :: IO ()

还有

execStateT (...) :: IO [String]

execStateT 返回计算的最终状态,您的状态是[String] 类型。通常,这可以通过不为main 声明类型并让某些a 推断为IO a 来解决。第二个我不确定,但也许是同一件事。

【讨论】:

  • 谢谢,第一个错误通过改成main :: IO [String] 解决了。我会更新问题。
【解决方案2】:

我要猜测一下你要做什么。

此程序可识别以下命令:

hist        -- show current history
add xxx     -- add xxx to the history list
clear       -- clear the history list
count       -- show the count of history items
quit        -- quit the command loop

节目来源:

import System.Console.Haskeline
import Control.Monad.Trans.Class
import Control.Monad.Trans.State.Strict
import Control.Monad

main :: IO ()
main = evalStateT (runInputT defaultSettings loop) []

loop :: InputT (StateT [String] IO) ()
loop = do
  minput <- getInputLine "% "
  case minput of
      Nothing -> return ()
      Just "quit" -> return ()
      Just input -> process input >> loop

process input = do
  let args = words input
  case args of
    []  -> return ()
    ("hist": _)     -> showHistory
    ("add" : x : _) -> lift $ modify (++ [x]) 
    ("clear": _)    -> lift $ modify (const [])
    ("count": _)    -> do hs <- lift get
                          outputStrLn $ "number of history items: " ++ show (length hs)
    _               -> outputStrLn "???"

showHistory = do
  hist <- lift get
  forM_ (zip [(1::Int)..] hist) $ \(i,h) -> do
    outputStrLn $ show i ++ " " ++ h

【讨论】:

  • 非常感谢,我会添加对我真正问题的描述。
【解决方案3】:

你有here的代码编译,它定义process为:

process :: Counter -> String -> IO ()

使用此签名创建process 的版本:

Counter -> String -> InputT (StateT [String] IO) ()

只需使用 liftIO:

process' :: Counter -> String -> InputT (StateT [String] IO) ()
process' counter str = liftIO $ process counter str

【讨论】:

  • 谢谢,它确实可以编译,但缺少历史逻辑。你会如何建议添加它?具体来说,如果 get 不在状态单子内或者我遗漏了什么,我将如何在 showHistory 中使用它?
  • 我也尝试从process' 拨打showHist,但被另一个error 卡住了。
猜你喜欢
  • 1970-01-01
  • 2011-05-07
  • 2023-02-09
  • 2019-12-02
  • 2012-05-19
  • 2020-05-04
  • 1970-01-01
  • 2014-10-23
  • 2011-08-11
相关资源
最近更新 更多