2016-06-20 7 views
3

Это последующий контроль за this question. Я пытаюсь совместить shell от @ ErikR's answer в моей петле InputT.Объединение состоянияT с 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 имеет встроенную поддержку истории, но я пытаюсь реализовать ее сам как упражнение.

Не стесняйтесь предлагать замену для монадных трансформаторов для получения одинаковой функциональности.

Моя реальная проблема

Я хотел бы добавить ipython как возможности для лямбда РЕПЛ в выпишу вам Haskell, а именно:

I. Счетчик для ввода и вывода, которые будут появляться в строке, т.е.

In[1]> 
Out[1]> 

Это уже done.

II. Сохраните каждую команду в истории (автоматически) и отобразите все предыдущие команды с помощью специальной команды, например. histInput (такой же, как hist в ipython). Кроме того, сохраните историю всех результатов вывода и покажите их с помощью histOutput. Это то, что я пытаюсь сделать в этом вопросе (входная история только на данный момент).

III. Ссылка на предыдущие входы и выходы, например. если In[1] был x, то In[1] + 2 следует заменить на x + 2, а также для вывода.

Update

Я пытался объединить @ 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 и давая ему вывод IO a для некоторого 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

Спасибо, он компилирует, но не хватает логики истории. Как бы вы предложили добавить его? В частности, как я смогу использовать 'get' в' showHistory', если он не находится в государственной монаде или мне что-то не хватает? – dimid

+0

Я также попытался вызвать 'showHist' из' process'' и застрял еще [ошибка] (http://stackoverflow.com/q/38071560/165753). – dimid