4

私は Haskell を学ぼうとしていますが、私が書こうとした小さなサンプル コードで、かなりの量の "Couldn't match expected type" エラーが発生しています。私が間違っていること/これについてどうすればよいかについて、誰かが私にガイダンスを与えることができますか?

これらはエラーですが、コードをどのように記述すればよいかよくわかりません。

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にロードして、たとえば入力すると: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

:演算子は、リストの先頭に1つの項目を追加するためのものです(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渡すことです(これはレイヤーでそれを再ラップします)。これはまさに演算子( bindと発音)が行うことです。この行は次のように書くことができます[String]schedulerSimpleMainIO>>=

createTask inFileArray >>= schedulerSimpleMain

ここで、演算子は結果を「パイプフォワード」するものと考えることができます>>=(Unixパイプ演算子に少し似ています)が、途中で必要なすべてのアンラップ/再ラップも実行します。

始めたばかりのときにバインド演算子を正しく使用するのは少し難しい場合があります。これがdo、最初に表記法が提供されている理由の1つです。このスニペットは次のように書くことができます

do newInFileArray <- createTask inFileArray
   schedulerSimpleMain newInFileArray

これは、上記で記述したコードの単なる構文糖衣ですが、バインド演算子に慣れていない場合は、もう少し読みやすくなります。

6行目では、別の関連する問題があります。シーケンス演算子;は、基本的に「左側で計算を行い、結果を無視してから、右側で計算を行う」ことを意味します。左側の計算には型が必要でIO aあり、右側の計算には型が必要IO bです(anyaおよびb)。

残念ながら、正しい計算のタイプは[String] -> IO [String]、であるため、この行はタイプチェックを行いません。schedulerSimpleMainこれを修正するには、適切な引数を次のように入力する必要があります。

do putStrLn "Invalid input."; schedulerSimpleMain inFileArray

これでタイプチェックが行われます。コード全体でこの種のエラーが発生します。ここでは、すべての修正について詳しく説明するつもりはありません。最初に自分で修正してみるべきだと思います。1日かそこらでまだ問題が発生している場合は、修正したコードをhpasteに配置して学習することができます。

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