我正在编写一个shell脚本(我在haskell中的第一个非示例),它应该列出一个目录,获取每个文件大小,执行一些字符串操作(纯代码),然后重命名一些文件.我不确定我做错了什么,所以有两个问题:
error:
Couldn't match expected type `[FilePath]'
against inferred type `IO [FilePath]'
In the second argument of `mapM', namely `fileNames'
In a stmt of a 'do' expression:
files <- (mapM getFileNameAndSize fileNames)
In the expression:
do { fileNames <- getDirectoryContents;
files <- (mapM getFileNameAndSize fileNames);
sortBy cmpFilesBySize files }
Run Code Online (Sandbox Code Playgroud)
码:
getFileNameAndSize fname = do (fname, (withFile fname ReadMode hFileSize))
getFilesWithSizes = do
fileNames <- getDirectoryContents
files <- (mapM getFileNameAndSize fileNames)
sortBy cmpFilesBySize files
Run Code Online (Sandbox Code Playgroud)
Ant*_*sky 13
您的第二个具体问题是您的功能类型.但是,你的第一个问题(不是真正的类型)是do声明中的getFileNameAndSize.虽然do与monad一起使用,但它不是monadic万能药; 它实际上是作为一些简单的翻译规则实现的.Cliff的Notes版本(由于一些涉及错误处理的细节,但是非常接近,这是不完全正确的)是:
do a ≡ ado a ; b ; c ... ≡ a >> do b ; c ...do x <- a ; b ; c ... ≡ a >>= \x -> do b ; c ...换句话说,getFileNameAndSize相当于没有do块的版本,所以你可以摆脱它do.这让你失望
getFileNameAndSize fname = (fname, withFile fname ReadMode hFileSize)
Run Code Online (Sandbox Code Playgroud)
我们可以找到这样的类型:因为它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
Run Code Online (Sandbox Code Playgroud)
接下来,正如KennyTM指出的那样,你有fileNames <- getDirectoryContents; 因为getDirectoryContents有类型FilePath -> IO FilePath,你需要给它一个参数.(例如 getFilesWithSizes dir = do fileNames <- getDirectoryContents dir ...).这可能只是一个简单的疏忽.
Mext,我们来到你的错误的核心:files <- (mapM getFileNameAndSize fileNames).我不确定它为什么会给你一个确切的错误,但我可以告诉你什么是错的.记住我们所知道的getFileNameAndSize.在您的代码中,它返回一个(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
Run Code Online (Sandbox Code Playgroud)
这一切都很好,并且会很好.但是,我可能会略有不同地写它.我的版本可能看起来像这样:
{-# 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
Run Code Online (Sandbox Code Playgroud)
(<=<是.函数组合运算符的monadic等价物.)首先:是的,我的版本更长.但是,我可能已经preservingF定义了某个地方,使两个等效的长度.*(fileSize如果它没有在其他地方使用,我甚至可以内联.)其次,我更喜欢这个版本,因为它涉及链接更简单的纯函数我们'已经写好了.虽然你的版本很相似,但我的(我觉得)更加精简,并使这方面的事情更加清晰.
所以这是对你如何构建这些东西的第一个问题的一个答案.我个人倾向于将我的IO锁定为尽可能少的函数 - 只需要直接触摸外部世界的函数(例如 main,与文件交互的任何东西)得到一个IO.其他所有东西都是普通的纯函数(如果它是monadic的话,只是出于一般原因而且只是monadic preservingF).然后我安排事情main,等等,只是纯函数的组合和链:main从IO-land 获取一些值; 然后它调用纯函数来折叠,转动和毁坏日期; 然后它变得更多IO值; 然后它运作更多; 这个想法是尽可能地将两个域分开,以便更多的组合非IO代码总是免费的,并且IO只有在必要时才能完成黑盒.
像这样的操作员<=<真正有助于编写代码,因为它们允许您操作与monadic 值(例如-world)交互的函数,IO就像操作普通函数一样.您还应该查看Control.Applicative的 function <$> liftedArg1 <*> liftedArg2 <*> ...表示法,它允许您将普通函数应用于任意数量的monadic(真正Applicative)参数.这非常适合摆脱虚假的<-s并将纯函数链接到monadic代码上.
*:我觉得preservingF,或者至少它的兄弟姐妹preserving :: (a -> b) -> a -> (a,b)应该在某个地方的包裹中,但我也找不到.
ken*_*ytm 10
getDirectoryContents是一个功能.你应该为它提供一个参数,例如
fileNames <- getDirectoryContents "/usr/bin"
Run Code Online (Sandbox Code Playgroud)
另外,类型getFileNameAndSize是FilePath -> (FilePath, IO Integer),你可以从ghci检查:
Prelude> :m + System.IO
Prelude System.IO> let getFileNameAndSize fname = do (fname, (withFile fname ReadMode hFileSize))
Prelude System.IO> :t getFileNameAndSize
getFileNameAndSize :: FilePath -> (FilePath, IO Integer)
Run Code Online (Sandbox Code Playgroud)
Prelude System.IO> :t mapM
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
-- # ^^^^^^^^
Run Code Online (Sandbox Code Playgroud)
您应该将其类型更改FilePath -> IO (FilePath, Integer)为与类型匹配.
getFileNameAndSize fname = do
fsize <- withFile fname ReadMode hFileSize
return (fname, fsize)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1310 次 |
| 最近记录: |