Ошибки при запуске программы - PullRequest
2 голосов
/ 01 июля 2019

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

Это код, который я придумал.Однако я понятия не имею, работает ли это, потому что я не могу пройти отладку ошибок.

import Control.Monad
import System.Directory
import System.FilePath
import System.Posix.Files

printDirectory :: FilePath -> IO [FilePath]
printDirectory = do
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      isDirectory <- doesDirectoryExist fry </>
        if isDirectory
           then do printDirectory
           else putStrLn fry
      putStrLn "Directory search completed"
    return

Ниже приведены мои сообщения об ошибках (извините, это немного длинно).Я понимаю, что часть моей логики может быть немного ошибочной, особенно с рекурсивным вызовом в операторе if.К сожалению, я не могу пройти отладку, чтобы даже начать исправлять логику.Может кто-нибудь помочь объяснить, почему я получаю ошибки и как их исправить.

- Сообщения об ошибках -

ass3.hs:13:9: error:
    • Couldn't match expected type ‘FilePath -> IO [FilePath]’
                  with actual type ‘[b0]’
    • In a stmt of a 'do' block:
        forM filesInCurDir
          $ \ fry
              -> do isDirectory <- doesDirectoryExist fry
                                     </> if isDirectory then ... else putStrLn fry
                    putStrLn "Directory search completed"
      In the expression:
        do let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
           forM filesInCurDir $ \ fry -> do ...
           return
      In an equation for ‘printDirectory’:
          printDirectory
            = do let filesInCurDir = ...
                 forM filesInCurDir $ \ fry -> ...
                 return
   |
13 |         forM filesInCurDir $ \fry -> do
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...

ass3.hs:14:31: error:
    • Couldn't match type ‘IO Bool’ with ‘[Char]’
      Expected type: FilePath
        Actual type: IO Bool
    • In the first argument of ‘(</>)’, namely ‘doesDirectoryExist fry’
      In a stmt of a 'do' block:
        isDirectory <- doesDirectoryExist fry
                         </> if isDirectory then do printDirectory else putStrLn fry
      In the expression:
        do isDirectory <- doesDirectoryExist fry
                            </> if isDirectory then do printDirectory else putStrLn fry
           putStrLn "Directory search completed"
   |
14 |                 isDirectory <-doesDirectoryExist fry</>
   |                               ^^^^^^^^^^^^^^^^^^^^^^

ass3.hs:14:50: error:
    • Couldn't match type ‘[Char]’ with ‘Char’
      Expected type: FilePath
        Actual type: [FilePath]
    • In the first argument of ‘doesDirectoryExist’, namely ‘fry’
      In the first argument of ‘(</>)’, namely ‘doesDirectoryExist fry’
      In a stmt of a 'do' block:
        isDirectory <- doesDirectoryExist fry
                         </> if isDirectory then do printDirectory else putStrLn fry
   |
14 |                 isDirectory <-doesDirectoryExist fry</>
   |                                                  ^^^

ass3.hs:15:28: error:
    • Couldn't match expected type ‘Bool’
                  with actual type ‘FileStatus -> Bool’
    • Probable cause: ‘isDirectory’ is applied to too few arguments
      In the expression: isDirectory
      In the second argument of ‘(</>)’, namely
        ‘if isDirectory then do printDirectory else putStrLn fry’
      In a stmt of a 'do' block:
        isDirectory <- doesDirectoryExist fry
                         </> if isDirectory then do printDirectory else putStrLn fry
   |
15 |                         if isDirectory
   |                            ^^^^^^^^^^^

ass3.hs:16:41: error:
    • Couldn't match type ‘FilePath -> IO [FilePath]’ with ‘[Char]’
      Expected type: FilePath
        Actual type: FilePath -> IO [FilePath]
    • Probable cause: ‘printDirectory’ is applied to too few arguments
      In a stmt of a 'do' block: printDirectory
      In the expression: do printDirectory
      In the second argument of ‘(</>)’, namely
        ‘if isDirectory then do printDirectory else putStrLn fry’
   |
16 |                                 then do printDirectory
   |                                         ^^^^^^^^^^^^^^

ass3.hs:17:30: error:
    • Couldn't match type ‘IO ()’ with ‘[Char]’
      Expected type: FilePath
        Actual type: IO ()
    • In the expression: putStrLn fry
      In the second argument of ‘(</>)’, namely
        ‘if isDirectory then do printDirectory else putStrLn fry’
      In a stmt of a 'do' block:
        isDirectory <- doesDirectoryExist fry
                         </> if isDirectory then do printDirectory else putStrLn fry
   |
17 |                         else putStrLn fry
   |                              ^^^^^^^^^^^^

ass3.hs:17:39: error:
    • Couldn't match type ‘[Char]’ with ‘Char’
      Expected type: String
        Actual type: [FilePath]
    • In the first argument of ‘putStrLn’, namely ‘fry’
      In the expression: putStrLn fry
      In the second argument of ‘(</>)’, namely
        ‘if isDirectory then do printDirectory else putStrLn fry’
   |
17 |                         else putStrLn fry
   |                                       ^^^

ass3.hs:18:17: error:
    • Couldn't match type ‘IO’ with ‘[]’
      Expected type: [()]
        Actual type: IO ()
    • In a stmt of a 'do' block: putStrLn "Directory search completed"
      In the expression:
        do isDirectory <- doesDirectoryExist fry
                            </> if isDirectory then do printDirectory else putStrLn fry
           putStrLn "Directory search completed"
      In the second argument of ‘($)’, namely
        ‘\ fry
           -> do isDirectory <- doesDirectoryExist fry
                                  </> if isDirectory then ... else putStrLn fry
                 putStrLn "Directory search completed"’
   |
18 |                 putStrLn "Directory search completed"
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

ass3.hs:19:9: error:
    • Couldn't match expected type ‘[b0]’
                  with actual type ‘a0 -> m0 a0’
    • Probable cause: ‘return’ is applied to too few arguments
      In a stmt of a 'do' block: return
      In the expression:
        do let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
           forM filesInCurDir $ \ fry -> do ...
           return
      In an equation for ‘printDirectory’:
          printDirectory
            = do let filesInCurDir = ...
                 forM filesInCurDir $ \ fry -> ...
                 return
   |
19 |         return  
   |         ^^^^^^

1 Ответ

6 голосов
/ 02 июля 2019

Да, сообщение об ошибке GHC может быть довольно странным, но я постараюсь рассказать вам об этом наборе.Первое сообщение об ошибке на самом деле является одним из самых сложных для понимания, поэтому давайте перейдем ко второму.Этот говорит, что:

  • , когда GHC смотрел на первый аргумент (</>), а именно выражение doesDirectoryExist fry
  • it EXPECTED найти FilePath (потому что первый аргумент оператора (</>), очевидно, должен быть FilePath)
  • , но вместо этого В действительности нашел IO Bool

Если вы проверите тип doesDirectoryExist, вы увидите, что - действительно - он принимает FilePath и возвращает IO Bool, поэтому GHC прав, вы не можете предоставить doesDirectoryExist fry (который имеет тип IO Bool) как FilePath.

Я не совсем уверен, какие пути вы пытались объединить с (</>), но если мы избавимся от этого оператораи полностью переформатировать следующее выглядит больше как то, что вы намеревались:

printDirectory :: FilePath -> IO [FilePath]
printDirectory = do
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory
         else putStrLn fry
      putStrLn "Directory search completed"
    return

Если вы перекомпилируете с этой версией, первое сообщение об ошибке немного изменится, но это все еще сбивает с толку.Тем не менее, второе сообщение об ошибке исчезло, так что ситуация улучшается!Третье сообщение об ошибке (теперь фактически второе сообщение об ошибке) такое же, как и раньше.В нем говорится:

  • , когда GHC смотрел на выражение fry (первый аргумент doesDirectoryExist)
  • it ожидаемое aFilePath
  • но это на самом деле нашел [FilePath]

Это странно!Мы ожидали, что FilePath тоже не весь список FilePath с.Вот что должен был сделать forM.Здесь произошло то, что из-за какой-то другой ошибки, которая не очевидна, GHC неправильно набрал fry как [FilePath] вместо FilePath.Чтобы обойти это, давайте подделать его, переопределив значение fry s с помощью оператора let:

printDirectory :: FilePath -> IO [FilePath]
printDirectory = do
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      let fry = "__FAKEFILEPATH__" -- DEBUGGING       -- << CHANGE HERE
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory
         else putStrLn fry
      putStrLn "Directory search completed"
    return

Если мы перекомпилируем, у нас останется три ошибки.Первая ошибка, такая же упрямая, как и прежде, сбивает с толку.Второе сообщение об ошибке - это вариант пятого сообщения в исходном списке:

Directory.hs:13:18: error:
      • Couldn't match expected type ‘IO ()’
                    with actual type ‘FilePath -> IO [FilePath]’
      • Probable cause: ‘printDirectory’ is applied to too few arguments
        In a stmt of a 'do' block: printDirectory
        In the expression: do printDirectory

Здесь GHC считает, что выражение do printDirectory должно иметь тип IO (), но вместо этогоимеет тип FilePath -> IO [FilePath], и он очень подсказывает, что вы вызвали printDirectory со слишком малым количеством аргументов (что верно, поскольку printDirectory нужен путь к файлу).Давайте пока предоставим fry, хотя нам, возможно, понадобится сделать что-то другое позже, чтобы получить правильную рекурсию.

printDirectory :: FilePath -> IO [FilePath]
printDirectory = do
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      let fry = "__FAKEFILEPATH__" -- DEBUGGING
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory fry -- FIXME      -- << CHANGE HERE
         else putStrLn fry
      putStrLn "Directory search completed"
    return

Однако это не совсем устраняет ошибку.Теперь GHC сообщает нам, что:

Directory.hs:14:15: error:
      • Couldn't match type ‘()’ with ‘[FilePath]’
        Expected type: IO [FilePath]
          Actual type: IO ()
      • In the expression: putStrLn fry
        In a stmt of a 'do' block:
          if isDirectory then do printDirectory fry else putStrLn fry

В основном, в Haskell ветви then и else оператора if должны иметь один и тот же тип, новы пытаетесь вернуть список файлов в одной ветви (потому что printDirectory возвращает тип IO [FilePath]), но print имя файла (которое имеет тип IO ()) в другой ветви.

Полагаю, вам нужно решить, хотите ли вы напечатать файлов или вернуть файлов.Вы сказали в своем вопросе, что хотите напечатать их, поэтому я предполагаю, что ваша printDirectory подпись неверна.Если вы просто печатаете, то это действие ввода-вывода, которое ничего не возвращает (или, по крайней мере, ничего полезного), поэтому подпись должна выглядеть следующим образом:

printDirectory :: FilePath -> IO ()

Если вы перекомпилируете, выполучить две ошибки.Первая такая же, как и раньше, вторая такая же, как и последняя ошибка в вашем исходном списке:

Directory.hs:15:5: error:
      • Couldn't match expected type ‘IO b0’
                    with actual type ‘a0 -> m0 a0’
      • Probable cause: ‘return’ is applied to too few arguments

Последняя строка вашей программы имеет странный фактический тип,К счастью, GHC объясняет, что вы, вероятно, забыли предоставить аргумент для return.На самом деле, неясно, что вы пытались вернуть сюда (и когда вы опубликовали свой код, вы, похоже, не указали его, поэтому, возможно, вы уже решили удалить это return).В любом случае, если мы отбросим его, у нас останется только одна ошибка:

Directory.hs:9:5: error:
      • Couldn't match expected type ‘FilePath -> IO ()’
                    with actual type ‘IO (IO ())’
      • In a stmt of a 'do' block:
          forM filesInCurDir ...

Здесь GHC решил, что оператор forM ... в вашем do-блоке должен был иметьтип FilePath -> IO (), но на самом деле он имел тип IO (IO b).

Это сбивает с толку, но действительный и ожидаемый тип неверны! Оператор forM должен был быть операцией ввода-вывода для печати группы файловых путей, поэтому он должен иметь тип IO ().

Вот что случилось. В Haskell тип do-block является типом его последнего оператора, и GHC каким-то образом решил, что весь внешний do-блок должен иметь тип FilePath -> IO (), поэтому он ожидает, что последний оператор будет иметь этот тип. Почему он считает, что внешний блок do должен иметь тип FilePath -> IO () вместо IO ()? Ну, потому что вы сказали, что printDirectory должен иметь тип FilePath -> IO (), а затем привязать блок do непосредственно к printDirectory, не задав printDirectory аргумент. Вам нужно написать printDirectory dir = do ..., вот так:

printDirectory :: FilePath -> IO ()
printDirectory dir = do                        -- << CHANGE HERE
    let filesInCurDir = getCurrentDirectory >>= getDirectoryContents
    forM filesInCurDir $ \fry -> do
      let fry = "__FAKEFILEPATH__" -- DEBUGGING
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory fry -- FIXME
         else putStrLn fry
      putStrLn "Directory search completed"

Теперь сообщение об ошибке гласит:

Directory.hs:9:5: error:
      • Couldn't match type ‘IO ()’ with ‘()’
        Expected type: IO ()
          Actual type: IO (IO ())
      • In a stmt of a 'do' block:
          forM filesInCurDir ...

Когда вы видите несоответствие IO xxx против IO (IO xxx) в вашем коде, это обычно потому, что вы написали оператор do-block как:

let x = something

когда это должно было быть:

x <- something

Здесь, если мы проверим тип getCurrentDirectory >>= getDirectoryContents в GHCi, мы увидим, что он имеет тип:

> :t getCurrentDirectory >>= getDirectoryContents
getCurrentDirectory >>= getDirectoryContents :: IO [FilePath]

так что это действие ввода-вывода для возврата списка путей к файлам. Однако мы присвоили его с let до filesInCurDir. Но мы не хотим, чтобы filesInCurDir было действием IO , мы хотим, чтобы это был фактический список файлов. Для этого нам нужно использовать <- вместо let:

printDirectory :: FilePath -> IO ()
printDirectory dir = do
    filesInCurDir <- getCurrentDirectory >>= getDirectoryContents    -- << CHANGE HERE
    forM filesInCurDir $ \fry -> do
      let fry = "__FAKEFILEPATH__" -- DEBUGGING
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory fry -- FIXME
         else putStrLn fry
      putStrLn "Directory search completed"

Теперь у нас все еще есть несовпадение типов в операторе forM, но мы приближаемся:

Directory.hs:9:5: error:
      • Couldn't match type ‘[()]’ with ‘()’
        Expected type: IO ()
          Actual type: IO [()]
      • In a stmt of a 'do' block:
          forM filesInCurDir

GHC ожидал, что forM будет иметь тип IO () (т. Е. Действие, которое выполняет некоторую печать и ничего не возвращает AKA "единицу" AKA ()), но вместо этого forM пытается вернуть целое список из (). Это происходит, когда вы используете forM (который создает список для возврата) вместо forM_ (который просто выполняет некоторые действия ввода-вывода для их побочных эффектов, таких как печать, но сам ничего не возвращает). Таким образом, вам нужно заменить forM на forM_, и теперь вы можете безопасно удалить оператор «ОТЛАДКА»:

printDirectory :: FilePath -> IO ()
printDirectory dir = do
    filesInCurDir <- getCurrentDirectory >>= getDirectoryContents
    forM_ filesInCurDir $ \fry -> do      -- << CHANGE HERE
                                          -- << REMOVE DEBUGGING
      isDirectory <- doesDirectoryExist fry
      if isDirectory
         then do printDirectory fry -- FIXME
         else putStrLn fry
      putStrLn "Directory search completed"

Проверяет тип без ошибок!

К сожалению, если вы попытаетесь запустить его, он просто войдет в бесконечный цикл, но это потому, что рекурсия нарушена. Содержимое каталога включает в себя специальные записи "." и "..", которые вы захотите пропустить, но даже если вы исправите это, функция фактически никогда не изменит текущий каталог, поэтому, если есть хотя бы один подкаталог, он будет просто продолжайте проверять текущий каталог снова и снова.

Итак, я думаю, что это все еще время отладки!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...