处理IO与haskell中的纯代码

时间:2010-05-29 07:50:19

标签: haskell io

我正在编写一个shell脚本(我在haskell中的第一个非示例),它应该列出一个目录,获取每个文件大小,执行一些字符串操作(纯代码),然后重命名一些文件。我不确定我做错了什么,所以有两个问题:

  1. 我应该如何安排此类程序中的代码?
  2. 我有一个具体的问题,我得到以下错误,我做错了什么?
  3. 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 }
    

    代码:

    getFileNameAndSize fname = do (fname,  (withFile fname ReadMode hFileSize))
    
    getFilesWithSizes = do
      fileNames <- getDirectoryContents
      files <- (mapM getFileNameAndSize fileNames)
      sortBy cmpFilesBySize files
    

2 个答案:

答案 0 :(得分:13)

您的第二个具体问题是您的功能类型。但是,您的第一个问题(不是真正的类型问题)是do中的getFileNameAndSize语句。虽然do与monad一起使用,但它不是monadic万能药;它实际上是some simple translation rules实现的。 Cliff的Notes版本(由于涉及错误处理的一些细节,但是非常接近,完全正确)是:

  1. do aa
  2. do a ; b ; c ...a >> do b ; c ...
  3. do x <- a ; b ; c ...a >>= \x -> do b ; c ...
  4. 换句话说,getFileNameAndSize相当于没有do块的版本,因此您可以摆脱do。这将离开你

    getFileNameAndSize fname = (fname, withFile fname ReadMode hFileSize)
    

    我们可以找到这种类型:因为fnamewithFile的第一个参数,所以它的类型为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 ...)。这可能只是一个简单的疏忽。

    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
    

    这一切都很好,并且会很好。但是,我可能略有不同地编写它。我的版本可能看起来像这样:

    {-# 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 
    

    <=<是函数组合运算符.的monadic等价物。)首先:是的,我的版本更长。但是,我可能已经在某处定义了preservingF,使两者的长度相等。*(如果没有在其他地方使用,我甚至可以内联fileSize。)其次,我更喜欢这个版本因为它涉及将我们已经编写的更简单的纯函数链接在一起。虽然你的版本很相似,但我的(我觉得)更加精简,使这方面的事情更加清晰。

    所以这是对你如何构建这些东西的第一个问题的一个答案。我个人倾向于将我的IO锁定为尽可能少的函数 - 只需要直接触摸外部世界的函数(例如 main以及与文件交互的任何内容)得到{ {1}}。其他所有东西都是普通的纯函数(如果它是monadic,由于一般原因,就像IO那样)。然后我安排事情,以便preservingF等只是纯函数的组合和链:mainmain获得一些值 - 土地;然后它调用纯函数来折叠,转动和毁坏日期;然后它获得更多IO个值;然后它运作更多;这个想法是尽可能地将两个域分开,这样非组合的非IO代码总是免费的,而黑盒IO只在必要时才能完成。< / p>

    IO这样的运算符确实有助于以这种方式编写代码,因为它们允许您操作与monadic值(例如<=< - world)交互的函数就像你在正常功能上操作一样。您还应该查看Control.Applicative's IO表示法,它允许您将普通函数应用于任意数量的monadic(真function <$> liftedArg1 <*> liftedArg2 <*> ...)个参数。这非常适合摆脱虚假的Applicative并将纯函数链接到monadic代码上。

    *:我觉得<-,或者至少它的兄弟preservingF应该放在某个地方,但我一直都找不到。

答案 1 :(得分:10)

getDirectoryContents is a function。你应该为它提供一个参数,例如

fileNames <- getDirectoryContents "/usr/bin"

此外,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)

但是mapM requires the input function to return an IO stuff

Prelude System.IO> :t mapM
mapM :: (Monad m) => (a -> m b) -> [a] -> m [b]
-- #                  ^^^^^^^^

您应该将其类型更改为FilePath -> IO (FilePath, Integer)以匹配类型。

getFileNameAndSize fname = do
  fsize <- withFile fname ReadMode hFileSize
  return (fname, fsize)