为什么有嵌套的IO monad,IO(IO())作为我函数的返回值?

DJG*_*DJG 5 haskell

为什么这个函数有类型: deleteAllMp4sExcluding :: [Char] -> IO (IO ()) 而不是deleteAllMp4sExcluding :: [Char] -> IO ()

另外,我怎么能重写这个以便它有一个更简单的定义?

这是函数定义:

import System.FilePath.Glob
import qualified Data.String.Utils as S

deleteAllMp4sExcluding videoFileName =
  let dirGlob = globDir [compile "*"] "."
      f = filter (\s -> S.endswith ".mp4" s && (/=) videoFileName s) . head . fst 
      lst = f <$> dirGlob
  in mapM_ removeFile <$> lst
Run Code Online (Sandbox Code Playgroud)

sep*_*p2k 17

<$>当应用于IOs时有类型(a -> b) -> IO a -> IO b.所以既然mapM_ removeFile有类型[FilePath] -> IO (),b在这种情况下IO (),结果类型就变成了IO (IO ()).

为避免这样嵌套,<$>当您尝试应用的函数产生IO值时,不应使用.相反,你应该使用>>=或,如果你不想改变操作数的顺序,=<<.


Lui*_*las 8

Riffing上sepp2k的答案,这是示区别一个很好的例子FunctorMonad.

标准的Haskell定义是Monad这样的(简化):

class Monad m where
    return :: a -> m a
    (>>=)  :: m a -> (a -> m b) -> m b
Run Code Online (Sandbox Code Playgroud)

但是,这不是可以定义类的唯一方法.另一种运行方式如下:

class Functor m => Monad m where
    return :: a -> m a
    join   :: m (m a) -> m a
Run Code Online (Sandbox Code Playgroud)

鉴于此,您可以定义>>=在以下方面fmapjoin:

(>>=)  :: Monad m => m a -> (a -> m b) -> m b
ma >>= f = join (f <$> ma)
Run Code Online (Sandbox Code Playgroud)

我们将在您遇到的问题的简化草图中看到这一点.你正在做什么可以像这样模式化:

ma       :: IO a
f        :: a -> IO b
f <$> ma :: IO (IO b)
Run Code Online (Sandbox Code Playgroud)

现在你被困了,因为你需要一个IO b,并且Functor该类没有任何可以让你从那里获得的操作IO (IO b).获得你想要的唯一方法是进入Monad,join操作正是解决它的方法:

join (f <$> ma) :: IO b
Run Code Online (Sandbox Code Playgroud)

但是通过join/ <$>定义>>=,这与以下相同:

ma >>= f :: IO a
Run Code Online (Sandbox Code Playgroud)

请注意,该Control.Monad库附带了一个版本join(根据return和编写(>>=)); 你可以把它放在你的函数中以获得你想要的结果.但最好的办法是要认识到你所要做的事情基本上是一元的,因此这<$>不是工作的正确工具.你把一个动作的结果喂给另一个动作; 本质上要求你使用Monad.