让我们仔细的categorize
类型比较类型的hashFile
,铭记我们想传递hashFile
作为参数传递给categorize
hashFile :: FilePath -> IO (Digest MD5) -- I simplified the MonadIO constraint
categorize :: Ord k => ( a -> k ) -> [a] -> M.Map k [a]
categorize hashFile
将不是类型检查,因为GHC将尝试匹配k
与IO (Digest MD5)
,但IO
没有Ord
实例。换句话说,IO (Digest MD5)
作为Map
的关键是没用的:你需要的是Digest MD5
s,而不是计算,当你执行它们时它最终会产生Digest MD5
。
你真正想要做的是运行所有的IO
计算,并将其结果(类型Digest MD5
)放入Map
。结果函数将返回IO (Map (Digest MD5) FilePath)
- IO
计算,运行时将返回Map (Digest MD5) FilePath
。
最简单的方法是调整categorize
以适合我们需要的类型。
categorize :: (Applicative f, Ord k) => (a -> f k) -> [a] -> f (M.Map k a)
categorize f = fmap M.fromList . traverse (\x -> fmap (, x) (f x))
(我正在使用TupleSections
。)首先让我们看看类型。由于IO
是Applicative
一个实例,(a -> f k)
与统一在以下限制FilePath -> IO (Digest MD5)
:
a ~ FilePath
f ~ IO
k ~ Digest MD5
所以categorize hashFile :: [FilePath] -> IO (M.Map (Digest MD5) FilePath)
,这是我们想要的类型。
现在我们来看看实现。 traverse :: Applicative f => (a -> f b) -> [a] -> f [b]
*(néemapM
)需要Applicative
函数,将它映射到列表上,并将结果一起打散到列表中。我们用它来把每个项目变成一个(result, item)
元组。这会产生一个f [(k, a)]
值。然后我fmap
M.fromList
结果产生f (M.Map k a)
。
*从技术上讲,traverse
具有更一般类型(Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)
。我通过采取t ~ []
来达到这个版本。
因为此实现使用M.fromList
,重复的项目将被丢弃。实际上,如果您不希望任何文件具有相同的内容,则MD5散列值将会不同,因此这不会成为问题。练习:如果我们想保持重复,这个变化如何?
1.您有'FilePath - > IO(Digest MD5)'。获得'FilePath - > IO(Digest MD5,FilePath)'// 2.接下来,建立一个'[IO(Digest MD5,FilePath)]'列表// 3.使用Traversable类来获得IO [( Digest MD5,FilePath)]'// 4.使用Functor和'categorize'获得所需的IO(Map(Digest MD5)[FilePath]) – hao