你好,我有以下问题: 我想逐行读取文件,然后将行写入另一个文件。但是,我想返回行数。
因此,在一个纯函数内部,我将使用一个累加器,例如:
function parameters=method 0 ......
method accu {end case scenario} =accu
method accu {not end case} = accu+1 //and other stuff
如何在不使用其他功能的情况下在do块中执行此操作?
具体示例
module Main where
import System.IO
import Data.Char(toUpper)
main::IO()
main=do
let inHandle=openFile "in.txt" ReadMode
let outHandle=openFile "out.txt" WriteMode
inHandle>>= \a ->outHandle>>= \b ->loop a b 0>>=print . show
loop::Handle->Handle->Int->IO Int
loop inh outh cnt=hIsEOF inh>>= \l ->if l then return elem
else
do
hGetLine inh>>=hPutStrLn outh
loop inh outh (cnt+1)
修改
重构了loop
获取其参数的方式
P.S 2 (在 K.A Buhr 彻底回应之后)
I。我真正想要实现的是main
方法的最后一个表达式。我想采用多个IO Actions
并将其结果绑定到一个方法。具体来说:< / p>
inHandle>>= \a ->outHandle>>= \b ->loop a b 0>>=print . show
在这种情况下,我不了解的是:
如果将inHandle>>=
提供给\a ->
,然后将结果传递给...>>=\b
,则外部作用域内的变量是否在\b
中闭包了?!
如果不是>>=\a->..>>= \a b
,不是吗?内部作用域不应该保存与外部作用域的结果相对应的参数吗?
消除辅助方法内的操作
我想知道这是否是一种将多个动作粘合在一起而又不会在do
块中的方法:
就我而言:
loop::Handle->Handle->Int->IO Int
loop inh outh cnt=hIsEOF inh>>= \l ->if l then return elem
else
do
hGetLine inh>>=hPutStrLn outh
loop inh outh (cnt+1)
我不能说这样的话吗:
if ... then ...
else
hPutStrLn=<<action1 [something] v2=<<action2 [something] loop inh outh (cnt+1)
something
可能是运算符?(我不知道这就是我要问的原因)的原因。
答案 0 :(得分:5)
看起来您对最后一个问题的答案仍然让您感到困惑。
tl; dr:停止使用>>=
和=<<
,直到掌握了do-block表示法,您可以通过谷歌搜索“了解haskell io”并进行大量操作来完成教程中的示例。
长答案...
首先,我建议暂时避免使用>>=
和=<<
运算符。即使它们有时被称为“绑定”,它们也不会将变量或参数绑定到方法或其他类似方法,并且似乎使您绊倒。您可能还会发现section about IO from "A Gentle Introduction to Haskell"有助于快速了解IO的工作原理。
以下是有关IO的简短说明,可能会对您有所帮助,它将为回答您的问题提供基础。 Google对“了解haskell io”进行了更深入的解释:
(1)在Haskell中,任何类型IO a
的值都是IO 操作。 IO操作就像一个配方,可用于(通过执行操作)执行一些实际的输入/输出,然后产生类型为a
的值。因此,类型为IO String
的值是一个动作,如果执行,它将执行一些输入/输出并产生类型为String
的值,而IO ()
则是一个动作,如果执行后,将执行一些输入/输出并产生类型为()
的值。在后一种情况下,由于类型()
的值是无用的,因此通常会执行类型为IO ()
的操作,因为它们具有I / O副作用,例如打印输出行。
(2)在Haskell程序中执行IO操作的唯一方法是为其赋予特殊名称main
。 (交互式解释器GHCi提供了更多执行IO操作的方法,但让我们忽略它。)
(3)可以使用do标记将IO操作组合为更大的IO操作。 do
块由以下形式的行组成:
act -- action to be executed, with the result
-- thrown away (unless it's the last line)
x <- act -- action to be executed, with the result
-- named @x@ for later lines
let y = expr -- add a name @y@ for the value of @expr@
-- for later lines, but this has nothing to
-- do with executing actions
在上述模板中,act
可以是任何求值是IO操作的表达式(例如,某些IO a
的类型为a
的值)。重要的是要了解do块本身不会执行任何IO操作。取而代之的是,它构建一个新的IO动作,该IO动作(在执行时)将按照它们在do块中出现的顺序执行给定的IO动作集,从而丢弃或命名执行这些动作所产生的值。通过执行整个do-block产生的值将是do-block的最后一行(必须是上面第一形式的行)产生的值。
因此,如果Haskell程序包括:
myAction :: IO ()
myAction = do
putStrLn "Your name?"
x <- getLine
let stars = "***"
putStrLn (stars ++ x ++ stars)
然后定义一个myAction
类型的值IO ()
,这是IO操作。它本身不执行任何操作,但是如果执行过,它将按其执行顺序在do块中执行每个IO操作(各种类型IO a
的类型a
的值)出现。通过执行myAction
产生的值将是最后一行产生的值(在这种情况下,类型为()
的值()
)。
有了这个解释,让我们解决您的问题。首先,我们如何编写Haskell程序以使用循环将行从一个文件复制到另一个文件,而忽略行计数问题?这是一种与您的代码示例非常相似的方法:
import System.IO
myAction :: IO ()
myAction = do
inHandle <- openFile "in.txt" ReadMode
outHandle <- openFile "out.txt" WriteMode
loop inHandle outHandle
hClose outHandle
hClose inHandle
在这里,如果我们检查GHCi中这些openFile
调用之一的类型:
> :t openFile "in.txt" ReadMode
openFile "in.txt" ReadMode :: IO Handle
>
我们看到它的类型为IO Handle
。也就是说,这是一个IO 操作,在执行时,它会执行一些实际的I / O(即打开文件的操作系统调用),然后产生类型为Handle
的值,这是代表打开文件句柄的Haskell值。在您的原始版本中,当您撰写时:
let inHandle = openFile "in.txt" ReadMode
所有这些操作都为IO操作分配了名称inHandle
-它实际上没有执行IO操作,因此也没有真正打开文件。特别是,类型inHandle
的{{1}}的值本身不是文件句柄,而只是用于产生文件句柄的IO操作(或“配方”)。>
在上述IO Handle
的版本中,我们使用了表示法:
myAction
表示,如果并且当执行由inHandle <- openFile "in.txt" ReadMode
命名的IO操作时,将从执行IO操作myAction
开始(即,该表达式的类型为{ {1}}),该执行将产生一个openFile "in.txt" ReadMode"
,其名称为IO Handle
。同上,下一行将产生并命名一个开放Handle
。然后,我们将这些打开的句柄传递给表达式inHandle
中的outHandle
。
现在,loop
可以这样定义:
loop inHandle outHandle
值得花一点时间来解释这一点。 loop
是一个函数,它带有两个参数,每个参数loop :: Handle -> Handle -> IO ()
loop inHandle outHandle = do
end <- hIsEOF inHandle
if end
then return ()
else do
line <- hGetLine inHandle
hPutStrLn outHandle line
loop inHandle outHandle
。当将其应用于两个句柄时,如表达式loop
中那样,结果值的类型为Handle
。这意味着它是一个IO操作,特别是由loop inHandle outHandle
定义中的外部do-block创建的IO操作。此do-block创建一个IO操作,该IO操作在执行时按顺序执行两个IO操作,如外部do-block的行所示。第一行是:
IO ()
,它执行IO操作loop
(类型为end <- hIsEOF inHandle
的值),并执行它(包括询问操作系统是否已经到达文件末尾的句柄表示的文件) hEof inHandle
),并将结果命名为IO Bool
-请注意,inHandle
将是类型end
的值。
do-block的第二行是整个end
语句。它产生类型为Bool
的值,因此执行第二个IO操作。 IO操作取决于if
的值。如果IO ()
为true,则IO操作将为end
的值,如果执行该操作,则将不执行任何实际的I / O,并将产生类型为{{1}的值end
}。如果return ()
为假,则IO操作将为内部do块的值。此内部do块是一个IO操作(值为()
类型),如果执行,将按顺序执行三个IO操作:
IO操作()
,类型为end
的值,该值将在执行时从IO ()
中读取一行并产生结果hGetLine inHandle
。按照do-block,此结果将被命名为IO String
。
IO操作inHandle
,类型为String
的值,执行后会将line
写入hPutStrLn outHandle line
。
IO操作IO ()
,对外部do-block产生的IO操作的递归使用,该外部do-block在执行时将重新开始整个过程,从EOF检查开始。
如果将这两个定义(用于line
和outHandle
)放在程序中,它们将无能为力,因为它们只是IO操作的定义。执行它们的唯一方法是命名其中一个loop inHandle outHandle
,如下所示:
myAction
当然,我们可以使用名称loop
代替main
来获得与整个程序相同的效果:
main :: IO ()
main = myAction
花一些时间将其与上面的“具体示例”进行比较,看看它有何不同和相似之处。特别是,您能找出我为什么写的信吗?
main
代替:
myAction
要修改此程序以对行进行计数,一种相当标准的方法是将count设为import System.IO
main :: IO ()
main = do
inHandle <- openFile "in.txt" ReadMode
outHandle <- openFile "out.txt" WriteMode
loop inHandle outHandle
hClose inHandle
hClose outHandle
loop :: Handle -> Handle -> IO ()
loop inHandle outHandle = do
end <- hIsEOF inHandle
if end
then return ()
else do
line <- hGetLine inHandle
hPutStrLn outHandle line
loop inHandle outHandle
函数的参数,并让end <- hIsEOF inHandle
if end
then ...
产生count的最终值。由于表达式if hIsEOF inHandle
then ...
是一个IO操作(上面,它的类型为loop
),要使其产生计数,我们需要像在您的操作中将其赋予类型loop
。例。它将仍然是IO操作,但是现在-当它执行时-它会产生一个有用的loop inHandle outHandle
值,而不是一个无用的IO ()
值。
要进行此更改,IO Int
必须使用起始计数器调用循环,命名其产生的值,然后将该值输出给用户。
绝对清楚。 Int
的值仍然是由do-block创建的IO操作。我们只是在修改do-block的其中一行。曾经是:
()
评估为类型main
的值,表示IO操作-当执行整个do-block时-将在轮到之前将行从一个文件复制到另一个文件时执行产生main
值被丢弃。现在,它将是:
loop inHandle outHandle
,其中右侧将求值为IO ()
类型的值,表示一个IO操作-当执行整个do-block时-将在轮到从以下位置复制行时执行将一个文件复制到另一个文件,然后生成类型为()
的计数值命名为count <- loop inHandle outHandle 0
,以供以后的do-block步骤使用。
无论如何,修改后的IO Int
看起来像这样:
Int
现在,我们重写count
以保持计数(通过递归调用将运行计数作为参数,并在执行IO操作时产生最终值):
main
整个程序是:
main :: IO ()
main = do
inHandle <- openFile "in.txt" ReadMode
outHandle <- openFile "out.txt" WriteMode
count <- loop inHandle outHandle 0
hClose inHandle
hClose outHandle
putStrLn (show count) -- could just write @print count@
现在,您询问了如何在不使用其他功能的情况下在do-block中使用累加器。我不知道您是要使用loop
以外的其他功能(在这种情况下,上面的答案可以满足要求),还是您根本不使用任何显式的loop :: Handle -> Handle -> Int -> IO Int
loop inHandle outHandle count = do
end <- hIsEOF inHandle
if end
then return count
else do
line <- hGetLine inHandle
hPutStrLn outHandle line
loop inHandle outHandle (count + 1)
。
如果是后者,则有两种方法。首先,import System.IO
main :: IO ()
main = do
inHandle <- openFile "in.txt" ReadMode
outHandle <- openFile "out.txt" WriteMode
count <- loop inHandle outHandle 0
hClose inHandle
hClose outHandle
putStrLn (show count) -- could just write @print count@
loop :: Handle -> Handle -> Int -> IO Int
loop inHandle outHandle count = do
end <- hIsEOF inHandle
if end
then return count
else do
line <- hGetLine inHandle
hPutStrLn outHandle line
loop inHandle outHandle (count + 1)
软件包中有单子循环组合器,可以使您执行以下操作(无需计数即可进行复制)。我还改用loop
代替显式的打开/关闭调用:
loop
您可以用单子状态计数行数:
monad-loops
关于从以上withFile
的定义中删除最后一个import Control.Monad.Loops
import System.IO
main :: IO ()
main =
withFile "in.txt" ReadMode $ \inHandle ->
withFile "out.txt" WriteMode $ \outHandle ->
whileM_ (not <$> hIsEOF inHandle) $ do
line <- hGetLine inHandle
hPutStrLn outHandle line
块,没有充分的理由这样做。这不像import Control.Monad.State
import Control.Monad.Loops
import System.IO
main :: IO ()
main = do
n <- withFile "in.txt" ReadMode $ \inHandle ->
withFile "out.txt" WriteMode $ \outHandle ->
flip execStateT 0 $
whileM_ (not <$> liftIO (hIsEOF inHandle)) $ do
line <- liftIO (hGetLine inHandle)
liftIO (hPutStrLn outHandle line)
modify succ
print n
块有开销,还是引入了一些额外的处理管道之类的东西。它们只是构造IO操作值的方法。因此,您可以替换:
do
使用
loop
但这是纯粹的语法更改。否则两者是相同的(并且几乎肯定会编译为等效代码)。