处理IO与Haskell中的纯代码 [英] dealing with IO vs pure code in haskell

查看:114
本文介绍了处理IO与Haskell中的纯代码的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm writing a shell script (my 1st non-example in haskell) which is supposed to list a directory, get every file size, do some string manipulation (pure code) and then rename some files. I'm not sure what i'm doing wrong, so 2 questions:


  1. 我应该如何安排这些程序中的代码?

  2. 我有一个特定的问题,我收到以下错误,我做错了什么?
  1. How should i arrange the code in such program?
  2. I have a specific issue, i get the following error, what am i doing wrong?





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 }

code:

code:

getFileNameAndSize fname = do (fname,  (withFile fname ReadMode hFileSize))

getFilesWithSizes = do
  fileNames <- getDirectoryContents
  files <- (mapM getFileNameAndSize fileNames)
  sortBy cmpFilesBySize files


推荐答案

你的第二个具体的问题是你的函数的类型。但是,你的第一个问题(不是类型的东西)是 getFileNameAndSize 中的 do 语句。虽然单元使用 do ,但它不是一个单方面的灵丹妙药;它实际上被实现为一些简单的翻译规则。 Cliff's Notes版本(它并不完全正确,这要归功于涉及错误处理的一些细节,但足够接近):

Your second, specific, problem is with the types of your functions. However, your first issue (not really a type thing) is the do statement in getFileNameAndSize. While do is used with monads, it's not a monadic panacea; it's actually implemented as some simple translation rules. The Cliff's Notes version (which isn't exactly right, thanks to some details involving error handling, but is close enough) is:


  1. 做一个 a

  2. 做一个; b; c ... a>>做b; c ...

  3. do x < - a; b; c ... a>> = \ x - >做b; c ...

  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 。这给你带来了:

In other words, getFileNameAndSize is equivalent to the version without the do block, and so you can get rid of the do. This leaves you with

getFileNameAndSize fname = (fname, withFile fname ReadMode hFileSize)

我们可以找到这个类型:因为 fname 是<$ c $的第一个参数c> withFile ,它具有类型 FilePath ;和 hFileSize 返回一个 IO Integer ,所以这就是 withFile ...的类型。。因此,我们有 getFileNameAndSize :: FilePath - > (FilePath,IO Integer)。这可能是也可能不是你想要的;您可以改为 FilePath - > IO(FilePath,Integer)。要改变它,你可以写任何

We can find the type for this: since fname is the first argument to withFile, it has type FilePath; and hFileSize returns an IO Integer, so that's the type of withFile .... Thus, we have getFileNameAndSize :: FilePath -> (FilePath, IO Integer). This may or may not be what you want; you might instead want FilePath -> IO (FilePath,Integer). To change it, you can write any of

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 ... )。这可能只是一个简单的疏忽。

Next, as KennyTM pointed out, you have fileNames <- getDirectoryContents; since getDirectoryContents has type FilePath -> IO FilePath, you need to give it an argument. (e.g. getFilesWithSizes dir = do fileNames <- getDirectoryContents dir ...). This is probably just a simple oversight.

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),就像我上面实现的那样。

Mext, we come to the heart of your error: files <- (mapM getFileNameAndSize fileNames). I'm not sure why it gives you the precise error it does, but I can tell you what's wrong. Remember what we know about getFileNameAndSize. In your code, it returns a (FilePath, IO Integer). However, mapM is of type Monad m => (a -> m b) -> [a] -> m [b], and so mapM getFileNameAndSize is ill-typed. You want getFileNameAndSize :: FilePath -> IO (FilePath,Integer), like I implemented above.

最后,我们需要修复最后一行。首先,虽然你不给我们,但是 cmpFilesBySize 大概是(FilePath,Integer)类型的函数 - > (FilePath,Integer) - >订购,比较第二个元素。但这很简单:使用 Data.Ord.comparing :: Ord a => (b→a)→> b - > b - >订购,您可以编写这个比较snd ,其类型为 Ord b => (a,b)→> (a,b)→>排序。其次,你需要返回你在IO monad中包含的结果,而不仅仅是一个普通的列表;函数 return :: Monad m => a - >

Finally, we need to fix your last line. First of all, although you don't give it to us, cmpFilesBySize is presumably a function of type (FilePath, Integer) -> (FilePath, Integer) -> Ordering, comparing on the second element. This is really simple, though: using Data.Ord.comparing :: Ord a => (b -> a) -> b -> b -> Ordering, you can write this comparing snd, which has type Ord b => (a, b) -> (a, b) -> Ordering. Second, you need to return your result wrapped up in the IO monad rather than just as a plain list; the function return :: Monad m => a -> m a will do the trick.

因此,把这些放在一起,你会得到

Thus, putting this all together, you'll get

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

这一切都很好,并且可以正常工作。但是,我可能会稍微不同地写它。我的版本可能如下所示:

This is all well and good, and will work fine. However, I might write it slightly differently. My version would probably look like this:

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

(<=< is the monadic equivalent of ., the function composition operator.) First off: yes, my version is longer. However, I'd probably already have preservingF defined somewhere, making the two equivalent in length.* (I might even inline fileSize if it weren't used elsewhere.) Second, I like this version better because it involves chaining together simpler pure functions we've already written. While your version is similar, mine (I feel) is more streamlined and makes this aspect of things clearer.

所以这对你的第一个问题有点回答如何构造这些东西。我个人倾向于将我的IO锁定为尽可能少的功能 - 只需要直接触及外部世界的功能(例如 main )以及任何其他功能与文件交互)得到 IO 。其他的东西都是一个普通的纯函数(如果它是一般性原因的单数形式的话,那么它是一元的),沿着 preservingF 的路线)。然后,我安排一些事情,使得 main 等等,只是纯函数的组合和链: main 得到一些值从 IO -land;那么它就称纯粹的功能来折叠,旋转和破坏日期;那么它会获得更多 IO 值;那么它运作得更多;等等。这个想法是尽可能地分开这两个域,以便更多构成的非 IO 代码总是空闲的,并且黑盒子<$ c $

So this is a bit of an answer to your first question of how to structure these things. I personally tend to lock my IO down into as few functions as possible—only functions which need to touch the outside world directly (e.g. main and anything which interacts with a file) get an IO. Everything else is an ordinary pure function (and is only monadic if it's monadic for general reasons, along the lines of preservingF). I then arrange things so that main, etc., are just compositions and chains of pure functions: main gets some values from IO-land; then it calls pure functions to fold, spindle, and mutilate the date; then it gets more IO values; then it operates more; etc. The idea is to separate the two domains as much as possible, so that the more compositional non-IO code is always free, and the black-box IO is only done precisely where necessary.

运算符(如< = )仅在必要时完成。 c>确实有助于以这种风格编写代码,因为它们允许您操作与单值相交互的函数(例如 IO -world )就像你在正常的功能上操作一样。您还应该查看控制.Applicative's 函数< $>提起Arg1 *提起Arg2 * ... 符号,它允许您将普通函数应用于任意数量的monadic(真正的 Applicative )参数。这对于摆脱伪造的< - s,并且仅仅通过一元代码链接纯函数是非常好的。

Operators like <=< really help with writing code in this style, as they let you operate on functions which interact with monadic values (such as the IO-world) just as you would operate on normal functions. You should also look at Control.Applicative's function <$> liftedArg1 <*> liftedArg2 <*> ... notation, which lets you apply ordinary functions to any number of monadic (really Applicative) arguments. This is really nice for getting rid of spurious <-s and just chaining pure functions over monadic code.

*:我觉得像 preservingF ,或者至少是它的同胞保留::(a - > b) - > a - > (a,b),应放在某个地方,但我一直找不到。

*: I feel like preservingF, or at least its sibling preserving :: (a -> b) -> a -> (a,b), should be in a package somewhere, but I've been unable to find either.

这篇关于处理IO与Haskell中的纯代码的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆