处理IO与Haskell中的纯代码 [英] dealing with IO vs pure code in 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:
- 我应该如何安排这些程序中的代码?
- 我有一个特定的问题,我收到以下错误,我做错了什么?
- How should i arrange the code in such program?
- 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:
-
做一个
≡a
-
做一个; b; c ...
≡a>>做b; c ...
-
do x < - a; b; c ...
≡a>> = \ x - >做b; c ...
do a
≡a
do a ; b ; c ...
≡a >> do b ; c ...
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 $ c $ )其次,我更喜欢这个版本,因为它涉及到我们已经编写的简单纯函数的链接。虽然你的版本是相似的,但我的(我感觉)更加简化,并使得这方面的事情更加清晰。
(<=<
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屋!