这可能是一个愚蠢的问题,但我无法在任何地方找到答案。我是Haskell新手,我遇到了I / O问题。
我有这个结构:
data SrcFile = SrcFile (IO Handle) String
srcFileHandle :: SrcFile -> IO Handle
srcFileHandle (SrcFile handle _) = handle
srcFileLine :: SrcFile -> String
srcFileLine (SrcFile _ string) = string
现在问题是我不知道如何将stdin / stderr / stdout分配到其中,因为stdin等是Handler,没有IO Handler。如果我使结构具有IO Handle的Handle属性,那么我将无法在其中添加任何其他文件句柄。
答案 0 :(得分:6)
从您对SrcFile
的定义来看,似乎您可能正在尝试在Haskell中编写C程序。语言shapes the way we think,好消息是Haskell是一种更强大的语言!
优秀的图书Real World Haskell有一个关于lazy I/O的部分。考虑摘录:
接近I / O的一种新方法是hGetContents功能。
hGetContents
的类型为Handle -> IO String
。它返回的String
表示句柄给出的文件中的所有数据。在严格评估的语言中,使用这样的函数通常是一个坏主意。读取2KB文件的全部内容可能没什么问题,但如果您尝试读取500GB文件的全部内容,则可能会因为缺少RAM来存储所有数据而崩溃。在这些语言中,传统上使用循环等机制来处理文件的整个数据。
这是激进的部分。
但是
hGetContents
是不同的。它返回的String
被懒惰地评估。在您拨打hGetContents
时,实际上没有任何内容被读取。仅在处理列表的元素(字符)时才从Handle
读取数据。由于不再使用String
的元素,Haskell的垃圾收集器会自动释放内存。所有这一切都完全透明地发生在你身上。既然你看起来像 - 而且真的是纯粹的String
,你可以把它传递给纯(非IO
)代码。
再往下是readFile
and writeFile
上的一个部分,它向您展示如何完全忘记句柄。
例如,假设您要从源文件中获取所有import
行:
module Main where
import Control.Monad (liftM, mapM_)
import Data.List (isPrefixOf)
import System.Environment (getArgs, getProgName)
import System.IO (hPutStrLn, stderr)
main :: IO ()
main = getArgs >>= go
where go [path] = collectImports `liftM` readFile path >>= mapM_ putStrLn
go _ = getProgName >>=
hPutStrLn stderr . ("Usage: " ++) . (++ " source-file")
collectImports :: String -> [String]
collectImports = filter ("import" `isPrefixOf`)
. takeWhile (\l -> null l
|| "module" `isPrefixOf` l
|| "import" `isPrefixOf` l)
. lines
即使main
的定义使用readFile
,该程序只会根据需要读取指定的源文件,而不是全部内容!没有什么神奇之处:请注意collectImports
使用takeWhile
仅检查所需的行,而不是检查必须读取所有行的filter
。
当输入自己的源时,程序输出
import Control.Monad (liftM, mapM_)
import Data.List (isPrefixOf)
import System.Environment (getArgs, getProgName)
import System.IO (hPutStrLn, stderr)
所以拥抱懒惰。懒惰是你的朋友!享受Haskell的其余美妙旅程。
答案 1 :(得分:4)
我不确定您真正尝试做什么,但您可以使用Handle
功能将IO Handle
转换为return
。所以,
stdin :: Handle
return stdin :: IO Handle
实际上,return
是一个多态函数。它的类型为a -> m a
,其中m
可以是IO
,Maybe
,[]
等。不要将它与C中的return
混淆 - 它是一个普通函数,而不是用于过早退出的关键字。
在您的代码中,您可以使用记录语法。以下内容是等效的,并自动将srcFileHandle
和srcFileLine
声明为函数:
data SrcFile = SrcFile { srcFileHandle :: IO Handle,
srcFileLine :: String }
答案 2 :(得分:2)
我不太了解你想要实现的目标。
IO a
表示:与外界的互动,在运行时会产生a
。
因此,在数据结构中存储IO Handle
没有意义。您只需存储句柄,您就可以使用句柄执行IO ,但是为了存储/加载它,您不需要进行IO交互。
因此,您的结构是:
data SrcFile = SrcFile Handle String
如果要更改/添加/操作内容,可以使用IORef
,它可以像 IO代码中的指针一样使用。