处理IO与haskell中的纯代码

Dra*_*sha 10 io haskell

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

  1. 我应该如何在这样的程序中安排代码?
  2. 我有一个具体问题,我得到以下错误,我做错了什么?
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版本(由于一些涉及错误处理的细节,但是非常接近,这是不完全正确的)是:

  1. do aa
  2. do a ; b ; c ...a >> do b ; c ...
  3. 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,等等,只是纯函数的组合和链:mainIO-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)

另外,类型getFileNameAndSizeFilePath -> (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)

mapM要求输入函数返回IO stuff:

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)