Haskell рекурсия в блоке do не работает (рекурсия находится в списке) - PullRequest
3 голосов
/ 01 мая 2020

Я довольно новичок в Haskell и хочу выполнить рекурсию в блоке do.

countLines :: String -> IO Int
countLines filePath = do
    isFile <- doesFileExist filePath
    if isFile
        then do contents <- readFile filePath
                print contents
                pure 0
        else do files <- getDirectoryContents filePath    
                [countLines(file) | file <- files] -- recursion here!
                pure 0

, пока я не добавлю это понимание списка, все будет работать нормально, но как только я добавлю это, я получу следующая ошибка:

Main.hs:59:17: error:
    • Couldn't match type ‘[]’ with ‘IO’
      Expected type: IO (IO Int)
        Actual type: [IO Int]
    • In a stmt of a 'do' block: [countLines (file) | file <- files]
      In the expression:
        do files <- getDirectoryContents filePath
           [countLines (file) | file <- files]
           pure 0
      In a stmt of a 'do' block:
        if isFile then
            do contents <- readFile filePath
               print contents
               pure 0
        else
            do files <- getDirectoryContents filePath
               [countLines (file) | file <- files]
               pure 0
   |
59 |                 [countLines(file) | file <- files]
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
bash-3.2$ 

Кто-нибудь знает, как это исправить?

Ответы [ 2 ]

1 голос
/ 01 мая 2020

Вы находитесь в монаде IO, поэтому каждая строка должна выдавать IO a, но ваше понимание списка дает ... ну, список [a]. К счастью, есть функция sequence_ :: (Foldable t, Monad m) => t (m a) -> m (), которая превратит ваш [IO ()] в один IO (), одновременно выполняя в нем все действия ввода-вывода. Таким образом, ваш else блок должен выглядеть следующим образом:

    else do files <- getDirectoryContents filePath    
            sequence_ [countLines(file) | file <- files]
            pure 0

Как упоминает Виллем, можно упростить их еще больше, используя mapM_ :: (Foldable f, Monad m) => (a -> m b) -> f a -> m ():

    else do files <- getDirectoryContents filePath    
            mapM_ countLines files
            pure 0
1 голос
/ 01 мая 2020

Понимание списка является выражением [IO Int], а не IO Int, в то время как список [] является членом класса типов Monad, это не тот, с которым вы сейчас работаете в do блок. Вы можете использовать mapM_ :: (Foldable f, Monad m) => (a -> m b) -> f a -> m () для работы с рекурсией здесь:

countLines :: String -> IO Int
countLines filePath = do
    isFile <- doesFileExist filePath
    if isFile
        then readFile filePath >>= print
        else getDirectoryContents filePath >>= <b>mapM_ countLines</b>
    pure 0

Как @ chi говорит , если вам нужно получить список результатов, вы должны использовать mapM :: (Traversable f, Monad m) => (a -> m b) -> t a -> m (t b). Таким образом, вы можете предоставить список результатов вызовов monadi c. Вы можете, например, суммировать их, чтобы получить результат. Я оставляю это как упражнение.

...