Да, сообщение об ошибке 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 ожидаемое a
FilePath
- но это на самом деле нашел
[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"
Проверяет тип без ошибок!
К сожалению, если вы попытаетесь запустить его, он просто войдет в бесконечный цикл, но это потому, что рекурсия нарушена. Содержимое каталога включает в себя специальные записи "."
и ".."
, которые вы захотите пропустить, но даже если вы исправите это, функция фактически никогда не изменит текущий каталог, поэтому, если есть хотя бы один подкаталог, он будет просто продолжайте проверять текущий каталог снова и снова.
Итак, я думаю, что это все еще время отладки!