我有一个Haskell函数,它在单个文件上运行以生成映射。我想迭代目录中的所有文件并应用此函数来生成单个映射。
我试图以这种方式接近它:
perFileFunc :: Int -> FilePath -> IO (Map.Map [Char] Double)
allFilesIn dir = filter (/= "..")<$>(filter(/= ".")<$>(getDirectoryContents dir)
这给了我一个目录中所有文件名的列表,除了。和..
现在我试着做
myFunc dir n = map (perFileFunc n) <$> allFilesIn dir
它没有做任何事情。我期待一个地图列表,我可能会使用unionWith(+)加入。
这似乎不是正确的方法。
答案 0 :(得分:6)
您的代码无法正常运行,因为(<$>)
用于将纯操作提升为monadic(实际应用)上下文,因此myFunc dir n
的类型为{{1} }};一个IO操作,在执行时,查找目录中的文件列表,并将每个操作映射到另一个IO操作,该操作在执行时生成所需的IO [IO (Map.Map [Char] Double)]
- 而不实际执行任何操作。这可能不是你想要的:)。
您希望执行一个函数,在列表的每个元素上返回一个monadic操作,并返回结果值的列表。这就是mapM的作用:
Map
所以你真正想要的是:
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
您使用myFunc dir n = allFilesIn dir >>= mapM (perFileFunc n)
因为(>>=)
本身就是一个monadic动作,并且您希望将其传递给期望其结果类型并返回另一个动作的函数(在本例中为allFilesIn dir
)
请注意,mapM
与mapM
不同,在map
中(并非每个monad的行为都像这样,但大多数都是这样),它会在返回列表之前执行每个操作;这意味着每个操作的结果必须共同适合内存,并且您将无法以递增方式处理结果。如果你想要,你需要的不是IO
,比如iteratees。
答案 1 :(得分:2)
了解Haskell的棘手问题是如何识别和组合IO操作。我们来看一些类型的签名。
dir :: FilePath
allFilesIn :: FilePath -> IO [FilePath]
perFileFunc :: Int -> FilePath -> IO (Map.Map [Char] Double)
现在,你说myFunc
:
我期待一张地图列表
因此,对于该功能,您需要类型签名
myFunc :: Int -> FilePath -> [Map.Map String Double]
当然,返回类型不能只是 [Map.Map String Double]
,因为我们需要执行一些IO才能评估myFunc
。因此,给定Int
和FilePath
,我们实际上希望返回类型为 IO动作,生成 a [Map.Map String Double]
:
myFunc :: Int -> FilePath -> IO [Map.Map String Double]
现在,让我们看一下我们将要创建此功能的IO动作。
allFilesIn dir :: IO [FilePath]
perFileFunc n :: FilePath -> IO (Map.Map String Double)
perFileFunc
实际上不是IO操作,但是一个函数,给定FilePath
会产生IO操作。所以,让我们看看......如果我们运行allFilesIn
操作,那么我们就可以使用该列表并在每个元素上运行perFileFunc n
。
myFunc dir n = do
files <- allFilesIn dir
???
那么???
点的内容是什么?我们可以使用[FilePath]
,因为我们使用<-
来运行操作allFilesIn dir
。我们有一个函数perFileFunc n :: FilePath -> IO (Map.Map String Double)
。我们希望结果的类型为IO [Map.Map String Double]
。
停止...... Hoogle时间!推广我们所拥有的组件(a = FilePath
,b = Map.Map String Double
),我们hoogle for [a] -> (a -> IO b) -> IO [b]
(假装我们还没有看到ehird的答案)。瞧,mapM
是我们正在寻找的神奇解决方案! (或forM
,只是flip mapM
)
myFunc dir n = do
files <- allFilesIn dir
mapM (perFileFunc n) files
如果你发现这个符号,你会发现它减少到了第三个回答:
myFunc dir n = allFilesIn dir >>= (\files -> mapM (perFileFunc n) files)
-- eta reduce (\x -> f x) ==> f
myFunc dir n = allFilesIn dir >>= mapM (perFileFunc n)