4

我正在尝试学习 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)

谢谢,如果这不是提出这样一个问题的正确地方,我们深表歉意。

4

2 回答 2

19

您的代码存在几个问题,需要不同程度的工作来修复。按照我发现它们的顺序,你有...

不正确的类型

你的很多类型签名都不正确。如果一个函数进行任何 I/O,它需要将其返回类型包装在IO. 例如,而不是

createTask :: [String] -> [String]

你需要有

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

这反映了执行 I/O 的事实createTask(它向用户询问任务的名称)。

幸运的是,解决这个问题很容易 - 只需删除所有类型签名!这听起来很疯狂,但它可能非常有帮助。GHC 具有强大的类型推断机制,这意味着通常无需您明确指定类型即可推断出类型。在您的程序中,所有类型都非常简单,可以推断,因此您可以删除所有类型签名,在 GHCi 中加载模块并输入 eg :t createTask,然后解释器将告诉您推断的类型(然后您可以将其添加到源)。

运算符优先级问题

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

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

这被 Haskell 解析为

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

不检查类型,因为左侧是 type IO (),右侧是 type String。这也很容易解决。你只需要写你想要的,要么

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

或者

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

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

不正确的列表连接

添加括号后,您的代码具有该行

return (inFileArray:task)

其中inFileArray是 类型[String]并且task是 类型String。想必你打算添加taskinFileArray.

:运算符用于将单个项目添加到列表的前面(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需要 a[String]但您将其传递给IO [String].

您需要做的是IO从 的结果中解开图层,并将结果createTask inFileArray传递[String]schedulerSimpleMain(将其重新包装在IO图层中)。这正是操作员>>=(发音为bind)所做的事情。您可以将此行写为

createTask inFileArray >>= schedulerSimpleMain

您可以将>>=运算符视为“向前传递”结果(有点像 Unix 管道运算符),但也可以在途中进行所有必要的解包/重新包装。

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

do newInFileArray <- createTask inFileArray
   schedulerSimpleMain newInFileArray

这只是我上面编写的代码的语法糖,但如果您对绑定运算符不满意,它会更具可读性。

在第 6 行中,您遇到了一个不同但相关的问题。排序运算符;本质上意味着“在左侧进行计算,忽略结果,然后在右侧进行计算”。它要求左计算具有类型IO a,右计算具有类型IO b(对于任何ab)。

不幸的是,您正确的计算类型为[String] -> IO [String],因此该行再次不进行类型检查。要更正它,您只需要确保将适当的参数提供给schedulerSimpleMain

do putStrLn "Invalid input."; schedulerSimpleMain inFileArray

现在进行类型检查。您的代码中到处都有这种错误。我不会在这里为您详细介绍所有修复程序。我认为您应该先尝试自己修复它。如果你在一天左右仍然遇到问题,我可以将更正后的代码放在hpaste 上供你学习。

于 2012-07-09T08:11:35.393 回答
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
于 2012-07-09T08:22:01.687 回答