объединение StateT с InputT

Это продолжение этого вопроса. Я пытаюсь объединить shell из ответа @ErikR в свой цикл 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 } }

Полный код можно найти здесь, он основан на Напишите вам haskell.

Я знаю, что haskelline имеет встроенную поддержку истории, но я пытаюсь реализовать ее самостоятельно в качестве упражнения.

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

Моя настоящая проблема

Я хотел бы добавить ipython подобных возможностей в лямбда-REPL в Write You a Haskell, а именно:

I. Счетчик для ввода и вывода, который появится в подсказке, т.е.

In[1]>
Out[1]>

Это уже сделано .

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

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

Обновить

Я попытался объединить ответ @ErikR и временно отключил 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) -> ... }

person dimid    schedule 20.06.2016    source источник
comment
haskeline уже реализует для вас историю командной строки — взгляните на пример использования   -  person ErikR    schedule 20.06.2016
comment
Спасибо, я знаю, но я хочу реализовать это сам в качестве упражнения.   -  person dimid    schedule 20.06.2016
comment
Если вы хотите реализовать историю самостоятельно, почему в вашем коде появляется InputT?   -  person ErikR    schedule 23.06.2016
comment
Он взят из оригинального кода Стивена Диля в WYAH. Мне не нужно InputT как таковое, мне просто нужно получить строку ввода с пользовательским приглашением и, таким образом, использовать getInputLine, тип которого (MonadException m) => String -> InputT m (Maybe String).   -  person dimid    schedule 23.06.2016


Ответы (3)


Первая ошибка заключается в том, что вы объявили

main :: IO ()

но также

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

execStateT возвращает конечное состояние вычисления, а ваше состояние имеет тип [String]. Обычно это исправляют, просто не объявляя тип для main и допуская, что он будет IO a для некоторого a. На счет второго не уверен, но, возможно, это то же самое.

person amalloy    schedule 20.06.2016
comment
Спасибо, первая ошибка решилась заменой на main :: IO [String] . Я обновлю вопрос. - person dimid; 20.06.2016

Я собираюсь предположить, что вы пытаетесь сделать.

Эта программа распознает следующие команды:

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
person ErikR    schedule 23.06.2016
comment
Большое спасибо, я добавлю описание моей реальной проблемы. - person dimid; 23.06.2016

Код, который у вас есть здесь компилируется и определяет 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
person ErikR    schedule 27.06.2016
comment
Спасибо, он компилируется, но ему не хватает логики истории. Как бы вы предложили добавить его? В частности, как я смогу использовать get в showHistory, если он не находится внутри монады состояния или я что-то упустил? - person dimid; 27.06.2016
comment
Я также пытался вызвать showHist из process' и застрял из-за другой ошибки. - person dimid; 28.06.2016