您的第二个问题是您的函数类型。但是,您的第一个问题(不是真正的类型)是
do
语句在
getFileNameAndSize
. 同时
做
与monads一起使用,它不是一种单一的灵丹妙药;它实际上实现为
some simple translation rules
. 克里夫的笔记版本(不是
确切地
是的,由于一些涉及错误处理的细节,但是非常接近)是:
-
do a
艾斯
a
-
do a ; b ; c ...
艾斯
a >> do b ; c ...
-
do x <- a ; b ; c ...
艾斯
a >>= \x -> do b ; c ...
换言之,
获取文件名和大小
相当于没有
做
阻止,这样你就可以摆脱
做
. 这就留给你
getFileNameAndSize fname = (fname, withFile fname ReadMode hFileSize)
我们可以找到这个的类型:因为
fname
第一个论点是
withFile
,它有类型
FilePath
和
hFileSize
返回一个
IO Integer
所以这就是
withFile ...
. 因此,我们有
getFileNameAndSize :: FilePath -> (FilePath, IO Integer)
. 这可能是你想要的,也可能不是你想要的。
FilePath -> IO (FilePath,Integer)
. 要更改它,您可以编写
getFileNameAndSize_do fname = do size <- withFile fname ReadMode hFileSize
return (fname, size)
getFileNameAndSize_fmap fname = fmap ((,) fname) $
withFile fname ReadMode hFileSize
-- With `import Control.Applicative ((<$>))`, which is a synonym for fmap.
getFileNameAndSize_fmap2 fname = ((,) fname)
<$> withFile fname ReadMode hFileSize
-- With {-# LANGUAGE TupleSections #-} at the top of the file
getFileNameAndSize_ts fname = (fname,) <$> withFile fname ReadMode hFileSize
接下来,正如Kennytm指出的,你已经
fileNames <- getDirectoryContents
自从
getDirectoryContents
有类型
FilePath -> IO FilePath
,你需要给它一个论点。(
例如
getFilesWithSizes dir = do fileNames <- getDirectoryContents dir ...
)这可能只是一个简单的疏忽。
下一步,我们来到你错误的核心:
files <- (mapM getFileNameAndSize fileNames)
. 我不知道它为什么会给你准确的错误,但我可以告诉你出了什么问题。记住我们所知道的
获取文件名和大小
. 在代码中,它返回
(FilePath, IO Integer)
. 然而,
mapM
属于类型
Monad m => (a -> m b) -> [a] -> m [b]
如此
mapM getFileNameAndSize
生病了。你想要
getFileNameAndSize :: FilePath -> IO (FilePath,Integer)
就像我在上面实现的。
最后,我们需要修复您的最后一行。首先,尽管你不给我们,
cmpFilesBySize
大概是一个函数类型
(FilePath, Integer) -> (FilePath, Integer) -> Ordering
,比较第二个元素。不过,这非常简单:使用
Data.Ord.comparing :: Ord a => (b -> a) -> b -> b -> Ordering
,你可以写这个
comparing snd
,具有类型
Ord b => (a, b) -> (a, b) -> Ordering
. 第二,您需要返回包含在IO Monad中的结果,而不是简单的列表;函数
return :: Monad m => a -> m a
会成功的。
因此,把这些放在一起,你会
import System.IO (FilePath, withFile, IOMode(ReadMode), hFileSize)
import System.Directory (getDirectoryContents)
import Control.Applicative ((<$>))
import Data.List (sortBy)
import Data.Ord (comparing)
getFileNameAndSize :: FilePath -> IO (FilePath, Integer)
getFileNameAndSize fname = ((,) fname) <$> withFile fname ReadMode hFileSize
getFilesWithSizes :: FilePath -> IO [(FilePath,Integer)]
getFilesWithSizes dir = do fileNames <- getDirectoryContents dir
files <- mapM getFileNameAndSize fileNames
return $ sortBy (comparing snd) files
这一切都很好,而且会很好地工作。然而,我
可以
写得稍有不同。我的版本可能如下所示:
{-# LANGUAGE TupleSections #-}
import System.IO (FilePath, withFile, IOMode(ReadMode), hFileSize)
import System.Directory (getDirectoryContents)
import Control.Applicative ((<$>))
import Control.Monad ((<=<))
import Data.List (sortBy)
import Data.Ord (comparing)
preservingF :: Functor f => (a -> f b) -> a -> f (a,b)
preservingF f x = (x,) <$> f x
-- Or liftM2 (<$>) (,), but I am not entirely sure why.
fileSize :: FilePath -> IO Integer
fileSize fname = withFile fname ReadMode hFileSize
getFilesWithSizes :: FilePath -> IO [(FilePath,Integer)]
getFilesWithSizes = return . sortBy (comparing snd)
<=< mapM (preservingF fileSize)
<=< getDirectoryContents
(
<=<
是一元等价于
.
,函数组合运算符。)首先:是的,我的版本更长。不过,我可能已经有了
preservingF
在某个地方定义,使这两个长度相等。*(我甚至可以内联
fileSize
第二,我更喜欢这个版本,因为它涉及到链接我们已经编写的简单的纯函数。虽然你的版本很相似,但我的(我觉得)更流线型,使这方面的事情更清楚。
所以这是对你的第一个问题的一个回答,关于如何构造这些东西。我个人倾向于将我的IO锁定在尽可能少的功能中,仅限于需要直接接触外部世界的功能。(
例如
main
任何与文件交互的东西
IO
. 其他的一切都是一个普通的纯函数(只有当它是单态的时候,因为一般的原因,沿着
保藏
)然后我安排事情
主要的
等等,只是纯函数的组成和链:
主要的
从中获取一些值
输入输出
-着陆;然后它调用纯函数来折叠、旋转和破坏日期;然后它得到更多
输入输出
值;然后它操作更多;等等。其思想是尽可能地将两个域分离开来,从而使更多的复合非-
输入输出
代码总是免费的,黑匣子
输入输出
只有在必要的时候才能做到。
类算子
<= & lt;
确实有助于以这种方式编写代码,因为它们允许您操作
功能
与一元值(如
输入输出
-世界)就像你在正常功能上操作一样。你也应该看看
Control.Applicative's
function <$> liftedArg1 <*> liftedArg2 <*> ...
符号,它允许您将普通函数应用于任意数量的一元(实际上
Applicative
)争论。这真的是很好的摆脱虚假
<-
在一元代码上链接纯函数。
*:我觉得
保藏
或者至少是它的兄弟姐妹
preserving :: (a -> b) -> a -> (a,b)
应该在某个包裹里,但我也找不到。