无法匹配预期类型 - Haskell代码

时间:2012-07-09 02:30:24

标签: haskell

我正在尝试学习Haskell,但是我尝试编写的一小段示例代码遇到了相当大的“无法匹配预期类型”错误。任何人都可以给我一些指导,告诉我我做错了什么/我应该怎么做?

这些是错误,但我不确定我应该如何编写代码。

toDoSchedulerSimple.hs:6:14:
    Couldn't match expected type `[t0]' with actual type `IO String'
    In the return type of a call of `readFile'
    In a stmt of a 'do' block: f <- readFile inFile
    In the expression:
      do { f <- readFile inFile;
           lines f }

toDoSchedulerSimple.hs:27:9:
    Couldn't match expected type `[a0]' with actual type `IO ()'
    In the return type of a call of `putStr'
    In a stmt of a 'do' block: putStr "Enter task name: "
    In the expression:
      do { putStr "Enter task name: ";
           task <- getLine;
           return inFileArray : task }

toDoSchedulerSimple.hs:34:9:
    Couldn't match expected type `IO ()' with actual type `[a0]'
    In a stmt of a 'do' block:
      putStrLn "Your task is: " ++ (inFileArray !! i)
    In the expression:
      do { i <- randomRIO (0, (length inFileArray - 1));
           putStrLn "Your task is: " ++ (inFileArray !! i) }
    In an equation for `getTask':
        getTask inFileArray
          = do { i <- randomRIO (0, (length inFileArray - 1));
                 putStrLn "Your task is: " ++ (inFileArray !! i) }

toDoSchedulerSimple.hs:41:9:
    Couldn't match expected type `[a0]' with actual type `IO ()'
    In the return type of a call of `putStr'
    In a stmt of a 'do' block:
      putStr "Enter the task you would like to end: "
    In the expression:
      do { putStr "Enter the task you would like to end: ";
           task <- getLine;
           filter (endTaskCheck task) inFileArray }

toDoSchedulerSimple.hs:60:53:
    Couldn't match expected type `IO ()'
                with actual type `[String] -> IO ()'
    In a stmt of a 'do' block: schedulerSimpleMain
    In the expression:
      do { (getTask inFileArray);
           schedulerSimpleMain }
    In a case alternative:
        "get-task"
          -> do { (getTask inFileArray);
                  schedulerSimpleMain }

这是代码本身。我认为这是相当简单的,但我的想法是通过调用其他函数来运行循环,接受输入并基于它执行操作。

import System.Random (randomRIO)
import Data.List (lines)

initializeFile :: [char] -> [String]
initializeFile inFile = 
    do  f <- readFile inFile
        let parsedFile = lines f
        return parsedFile

displayHelp :: IO()
displayHelp =
    do  putStrLn "Welcome to To Do Scheduler Simple, written in Haskell."
        putStrLn "Here are some commands you might find useful:"
        putStrLn "    'help' : Display this menu."
        putStrLn "    'quit' : Exit the program."
        putStrLn "    'new-task' : Create a new task."
        putStrLn "    'get-task' : Randomly select a task."
        putStrLn "    'end-task' : Mark a task as finished."
        putStrLn "    'view-tasks' : View all of your tasks."

quit :: IO()
quit = 
    do  putStrLn "We're very sad to see you go...:("
        putStrLn "Come back soon!"

createTask :: [String] -> [String]
createTask inFileArray = 
    do  putStr "Enter task name: "
        task <- getLine
        return inFileArray:task

getTask :: [String] -> IO()
getTask inFileArray = 
    do  i <- randomRIO (0, (length inFileArray - 1))
        putStrLn "Your task is: " ++ (inFileArray !! i)

endTaskCheck :: String -> String -> Bool
endTaskCheck str1 str2 = str1 /= str2

endTask :: [String] -> [String]
endTask inFileArray =
    do  putStr "Enter the task you would like to end: "
        task <- getLine
        return filter (endTaskCheck task) inFileArray

viewTasks :: [String] -> IO()
viewTasks inFileArray =
    case inFileArray of
        [] -> do putStrLn "\nEnd of tasks."
        _ -> do putStrLn (head inFileArray)
                viewTasks (tail inFileArray)

schedulerSimpleMain :: [String] -> IO()
schedulerSimpleMain inFileArray =
    do  putStr "SchedulerSimple> "
        input <- getLine
        case input of
            "help" -> displayHelp
            "quit" -> quit
            "new-task" -> schedulerSimpleMain (createTask inFileArray)
            "get-task" -> do (getTask inFileArray); schedulerSimpleMain
            "end-task" -> schedulerSimpleMain (endTask inFileArray)
            "view-tasks" -> do (viewTasks inFileArray); schedulerSimpleMain
            _ -> do putStrLn "Invalid input."; schedulerSimpleMain

main :: IO()
main = 
    do  putStr "What is the name of the schedule? "
        sName <- getLine
        schedulerSimpleMain (initializeFile sName)

谢谢,如果这不是提出这样问题的正确位置,请道歉。

2 个答案:

答案 0 :(得分:18)

您的代码存在多个问题,需要修改不同级别的工作。按照我发现它们的顺序,你有......

类型不正确

很多类型签名都不正确。如果函数完全执行任何I / O,则需要将其返回类型包装在IO中。例如,而不是

createTask :: [String] -> [String]

你需要

createTask :: [String] -> IO [String]

反映了createTask执行I / O的事实(它要求用户提供任务名称)。

幸运的是,修复此问题很简单 - 只需删除所有类型的签名即可!这听起来很疯狂,但它可能非常有用。 GHC具有强大的类型推断机制,这意味着通常可以在不明确指定类型的情况下推断类型。在您的程序中,所有类型很简单,可以推断,因此您可以删除所有类型的签名,在GHCi中加载模块并输入例如:t createTask,然后解释器会告诉你推断的类型(然后你可以添加到源中)。

运营商优先权问题

在Haskell中,函数应用程序具有最严格的绑定。特别是,当你写

putStrLn "Your task is: " ++ (inFileArray !! i)

这被Haskell解析为

(putStrLn "Your task is: ") ++ (inFileArray !! i)

不进行类型检查,因为左侧是IO ()类型,右侧是String类型。这也很容易修复。你只需要写出你想要的东西,这是

putStrLn ("Your task is: " ++ (inFileArray !! i))

putStrLn $ "Your task is: " ++ (inFileArray !! i)

其中运算符$表示“具有最低可能优先级的函数应用程序”,通常用于避免使用括号。

列表连接不正确

添加括号后,您的代码有

return (inFileArray:task)

其中inFileArray的类型为[String],而task的类型为String。大概您打算将task添加到inFileArray的末尾。

:运算符用于将单项添加到列表的(O(1)操作)。您不能使用它将项添加到列表的末尾(O(n)操作)。 Haskell中的所有列表都是链表,因此在列表前面添加项目与将其添加到最后是完全不同的。你想要

return (task:inFileArray)

将任务添加到列表的前面,或

return (inFileArray ++ [task])

task创建一个新的单元素列表,并使用列表连接运算符++将其添加到列表的末尾。

误解符号和>>=

这是您的代码中最基本的误解,需要最多的工作来解释。让我们看一下以下(经过高度编辑的)代码片段:

schedulerSimpleMain :: [String] -> IO ()                                    -- 1
schedulerSimpleMain inFileArray =                                           -- 2
    do  input <- getLine                                                    -- 3
        case input of                                                       -- 4
            "new-task" -> schedulerSimpleMain (createTask inFileArray)      -- 5
            _          -> do putStrLn "Invalid input."; schedulerSimpleMain -- 6

我们已经知道createTask的类型是[String] -> IO [String]。因此第5行不进行类型检查。函数schedulerSimpleMain需要[String],但您传递IO [String]

您需要做的是从IO的结果中打开createTask inFileArray图层,并将生成的[String]传递给schedulerSimpleMain(将其重新包装在IO>>=图层)。这正是运算符createTask inFileArray >>= schedulerSimpleMain (发音为 bind )的作用。您可以将此行写为

>>=

您可以将do运算符视为“管道转发”结果(有点像Unix管道运算符),但也可以在路上进行所有必要的展开/重新处理。

刚刚开始时正确使用绑定运算符可能有点棘手,这是我们首先提供do newInFileArray <- createTask inFileArray schedulerSimpleMain newInFileArray 表示法的原因之一。您可以将此代码段写为

;

这只是我上面编写的代码的语法糖,但如果你对bind运算符不熟悉,它会更容易理解。

在第6行中,您有一个不同但相关的问题。排序运算符IO a实质上意味着“在左边进行计算,忽略结果,然后在右边进行计算”。它要求左计算具有类型IO b,并且正确计算要具有类型a(对于任何b[String] -> IO [String])。

不幸的是,你的正确计算的类型为schedulerSimpleMain,所以这一行也不会进行类型检查。要更正它,您只需确保将适当的参数提供给do putStrLn "Invalid input."; schedulerSimpleMain inFileArray

{{1}}

现在有什么样的。你的代码中都有这种错误。我不会在这里详细介绍所有修复方法。我想你应该先尝试自己解决。如果您在一天左右的时间内仍然遇到问题,我可以将更正的代码放在hpaste上供您学习。

答案 1 :(得分:1)

我建议你用较小的位来打破你的程序并逐个测试它们。 我已经修复了你的几个功能:你可以为其他功能做同样的事情。

import System.Random (randomRIO)
import Data.List (lines)

-- ERROR
-- f.hs:6:14:
--     Couldn't match expected type `[t0]' with actual type `IO String'
--     In the return type of a call of `readFile'
--     In a stmt of a 'do' block: f <- readFile inFile
--     In the expression:
--       do { f <- readFile inFile;
--                     let parsedFile = lines f;
--                                    return parsedFile }
-- WHY?
-- initializeFile reads a file, therefore it must live inside the IO monad

initializeFile :: String -> IO [String]
initializeFile inFile = do
        f <- readFile inFile
        let parsedFile = lines f
        return parsedFile

quit :: IO()
quit = do
    putStrLn "We're very sad to see you go...:("
    putStrLn "Come back soon!"

-- ERROR
-- f.hs:76:44:
--     Couldn't match expected type `IO ()'
--                 with actual type `[String] -> IO ()'
--     In a stmt of a 'do' block: schedulerSimpleMain
--     In the expression:
--       do { putStrLn "Invalid input.";
--                     schedulerSimpleMain }
--     In a case alternative:
--         _ -> do { putStrLn "Invalid input.";
--                                   schedulerSimpleMain }
-- WHY?
-- in the "_" case, schedulerSimpleMain is called without parameters, but
-- it needs a [String] one.

schedulerSimpleMain :: [String] -> IO()
schedulerSimpleMain inFileArray = do
    putStr "SchedulerSimple> "
    input <- getLine
    case input of
        "quit" -> quit
        _ -> do putStrLn "Invalid input."; schedulerSimpleMain inFileArray

main :: IO()
main = do
    putStr "What is the name of the schedule? "
    sName <- getLine
    -- Extract the lines from the IO monad
    ls <- initializeFile sName
    -- Feed them to the scheduler
    schedulerSimpleMain ls