2016-06-20 36 views
3

這是this question的後續行動。我試圖將@ ErikR的shell合併到我的InputT循環中。結合StateT與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變壓器建議更換以獲得相同的功能。

我真正的問題

我想補充ipython像能力,以拉姆達REPL在給你寫的Haskell,即:

一用於輸入和輸出計數器,將出現在提示符下,即

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) -> ... } 
+0

haskeline已經實現了命令行歷史爲你 - 看看在[使用示例](https://hackage.haskell.org/ package/haskeline-0.7.2.3/docs/System-Console-Haskeline.html) – ErikR

+0

謝謝,我知道,但我想自己實現它作爲練習。 – dimid

+0

如果你想自己實現歷史,爲什麼'InputT'出現在你的代碼中? – ErikR

回答

1

第一個錯誤是因爲您已經聲明

main :: IO() 

而且

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

execStateT返回計算的最終狀態,你的狀態是[String]類型。通常這是固定的,只是沒有聲明main的類型,並且假定它對於某些a被推斷爲IO a。第二個我不確定,但也許是同樣的事情。

+0

謝謝,第一個錯誤通過更改爲'main :: IO [String] '來解決。我會更新這個問題。 – dimid

1

我會猜測你想做什麼。

這個程序可以識別以下命令:

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 
+0

非常感謝,我將添加我的真實問題的描述。 – dimid

0

你有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 
+0

謝謝,它編譯,但缺乏歷史邏輯。你會如何建議添加它?具體來說,如果我不能在state monad中使用'showHistory'中的'get',或者我錯過了什麼? – dimid

+0

我也嘗試從'process''調用'showHist',並被另一個[error](http://stackoverflow.com/q/38071560/165753)卡住了。 – dimid