Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stdin as IO Handle

Tags:

haskell

this may be a stupid question but i couldn't find answer anywhere. I'm a Haskell newbie and i'm having trouble with I/O.

I have this structure:

data SrcFile = SrcFile (IO Handle) String

srcFileHandle :: SrcFile -> IO Handle
srcFileHandle (SrcFile handle _) = handle

srcFileLine :: SrcFile -> String
srcFileLine (SrcFile _ string) = string

Now the problem is that i have no idea how to assign stdin/stderr/stdout into it, because the stdin etc are Handlers, no IO Handlers. And if i make the structure have Handle attributes insted of IO Handle, then i won't be able to add any other file handles into it.

like image 668
Arg Avatar asked Dec 17 '22 22:12

Arg


1 Answers

Judging from your definition of SrcFile, it seems as though you may be trying to write a C program in Haskell. Language shapes the way we think, and the good news is Haskell is a much more powerful language!

The excellent book Real World Haskell has a section on lazy I/O. Consider an excerpt:

One novel way to approach I/O is the hGetContents function. hGetContents has the type Handle -> IO String. The String it returns represents all of the data in the file given by the Handle.

In a strictly-evaluated language, using such a function is often a bad idea. It may be fine to read the entire contents of a 2KB file, but if you try to read the entire contents of a 500GB file, you are likely to crash due to lack of RAM to store all that data. In these languages, you would traditionally use mechanisms such as loops to process the file's entire data.

Here's the radical part.

But hGetContents is different. The String it returns is evaluated lazily. At the moment you call hGetContents, nothing is actually read. Data is only read from the Handle as the elements (characters) of the list are processed. As elements of the String are no longer used, Haskell's garbage collector automatically frees that memory. All of this happens completely transparently to you. And since you have what looks like—and, really, is—a pure String, you can pass it to pure (non-IO) code.

Further down is a section on readFile and writeFile that shows you how to forget about handles entirely.

For example, say you want to grab all the import lines from a source file:

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

Even though the definition of main uses readFile, the program reads only as much of the named source-file as necessary, not the whole thing! There's nothing magic going on: note that collectImports uses takeWhile to examine only those lines it needs to rather than, say, filter that would have to read all lines.

When fed its own source, the program outputs

import Control.Monad (liftM, mapM_)
import Data.List (isPrefixOf)
import System.Environment (getArgs, getProgName)
import System.IO (hPutStrLn, stderr)

So embrace laziness. Laziness is your friend! Enjoy the rest of the wonderful journey with Haskell.

like image 113
Greg Bacon Avatar answered Jan 07 '23 04:01

Greg Bacon