2016-05-17 52 views
4

我刚开始学习Haskell。如何在Haskell中编写函数和monad动作

我正在创建一个程序,查找重复的文件。我创建了以下功能:

hashFile :: (MonadIO m) => FilePath -> m (Digest MD5) 
categorize :: Ord k => (a -> k) -> [a] -> Map.Map k [a] 

而且我要撰写他们返回

Map.Map (Digest MD5) [FilePath]

我的问题是一个功能:

我无法找到一个方法来处理IO monad得到我想要的。所以我的问题是:

  1. 是我想要做的是正确的,还是应该返回类型将 真的是Map.Map (IO (Digest MD5)) [FilePath]

  2. 我该如何组合这些函数来获取按散列分组的文件列表?

+2

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

回答

6

让我们仔细的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将尝试匹配kIO (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。)首先让我们看看类型。由于IOApplicative一个实例,(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)]值。然后我fmapM.fromList结果产生f (M.Map k a)

*从技术上讲,traverse具有更一般类型(Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)。我通过采取t ~ []来达到这个版本。

因为此实现使用M.fromList,重复的项目将被丢弃。实际上,如果您不希望任何文件具有相同的内容,则MD5散列值将会不同,因此这不会成为问题。练习:如果我们想保持重复,这个变化如何?

+0

@AndPos:“它帮助我理解如何使用monad”这里重要的一点应该是,你不*需要一个monad,这个应用函数就足够了。 – Cactus

+0

@Cactus我不同意这是重要的一点。这是一个重点,但它很微妙,对于99%的应用程序来说可能并不重要,而理解如何使用与'Monad'相关的接口来使用'IO'的重要性需要完成在100%有用的应用程序。 –

+2

@DanielWagner:我认为在初学者的材料中,monadic接口的代表性过大,所以我从不放弃提倡适用的接口。 – Cactus