Преобразование [строки ввода-вывода] в строку ввода-вывода при вычислении анализируемого выражения

#parsing #haskell

#синтаксический анализ #haskell

Вопрос:

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

Проблема evalString заключается в функции. Функция выполняется отлично, если runIOThrows $ liftM show $ evalStatement env (x!!0) , например, это последняя строка. Я чувствовал, что естественным шагом было использовать map , но это просто дает мне [IO String] , а не IO String .

Однако, если я возвращаю функцию [IO String] , возникает ошибка с readStatement функцией и функцией evalAndPrint:

 ----- readStatement -----
Couldn't match typeIO’ with ‘[]’
      Expected type: [[HStatement]]
        Actual type: IO [HStatement]

----- evalAndPrint -----
Couldn't match type ‘[]’ with ‘IO
      Expected type: IO ()
        Actual type: [()]

Couldn't match typeIO’ with ‘[]’
      Expected type: IO String -> [()]
        Actual type: String -> IO ()
 

У меня создается впечатление, что есть гораздо более простой способ добиться желаемого эффекта при использовании map . Если бы я выполнял каждый оператор последовательно, тогда все работало бы отлично, поэтому, возможно, я мог бы использовать map для оценки n-1 операторов, а затем выполнить nth их вручную?

 parseProgram :: Parser [HStatement]
parseProgram = spaces *> many (parseEvalHVal <* spaces)

readStatement :: String -> IO [HStatement]
readStatement input = do
         program <- readFile input
         case parse parseProgram "fyp" program of
           Left err -> fail $ show err
           Right parsed -> return $ parsed

evalAndPrint :: Env -> String -> IO ()
evalAndPrint env expr = evalString env expr >>= putStrLn

evalString :: Env -> String -> IO String
evalString env expr = do
         x <- readStatement expr
         putStrLn $ show x
         map (exprs -> runIOThrows $ liftM show $ evalStatement env exprs) x

run :: String -> IO ()
run expr = nullEnv >>= flip evalAndPrint expr

main :: IO ()
main = do
         args   <- getArgs
         run $ args !! 0

runIOThrows :: IOThrowsError String -> IO String
runIOThrows action = runExceptT (trapError action) >>= return . extractValue


 

Комментарии:

1. Подобно моему комментарию к принятому ответу, liftM это древняя реликвия: современный код использует fmap или (<$>) . Конечно, это просто вопрос стиля, и если вы уже глубоко привыкли к liftM нему, вам не повредит придерживаться его (кроме того, что оно применяется реже), но если вы можете выбрать тот, к которому можно привыкнуть fmap , это более модно.

Ответ №1:

Вы можете использовать mapM для выполнения шагов IO , а затем получить список строк:

 evalString :: Env -> String -> IO [String]
evalString env expr = do
         x <- readStatement expr
         putStrLn (show x)
         mapM (runIOThrows . liftM show . evalStatement env) x 

Это, конечно, дает нам список строк. Если вы хотите обработать этот список после обработки, например, объединить строки, вы можете fmap это:

 evalString :: Env -> String -> IO String
evalString env expr = do
         x <- readStatement expr
         putStrLn (show x)
         concat <

gt; mapM (runIOThrows . liftM show . evalStatement env) x

Комментарии:

1. Это идеально! Я предполагаю, что mapM это можно использовать во всех случаях, когда требуется сопоставление при работе с типом IO a , если это имеет какой-то смысл?

2. @JiangShi К вашему сведению traverse , является более современным эквивалентом mapM . Если вы собираетесь изучить один из них, я предлагаю использовать traverse , поскольку он более широко применим и делает то же самое.