Why does Haskell NoBuffering option still seem to buffer? - haskell

I loaded up a file in ghci with the following:
h <- openFile "somefile.txt" ReadMode
hSetBuffering h NoBuffering
I then modified and saved somefile.txt in a text editor. When I call hGetChar several times in ghci, I receive the old characters of the file (as if the entire file was buffered when I opened it). I expected to calls of hGetChar to return the modified contents. Why is this not the case?
Edit:
The reason why it isn't showing the modified contents in the case decribed above is indeed because of the text editor. When the cat command is used instead (cat > somefile.txt), then the modified file contents is returned.
However, it does still seem to doing buffering. Say the file contents is as follows:
ABCDEFGHI
123456789
If I run hGetChar I get the 'A' as expected.
Now if I use cat (cat > somefile.txt) to change the contents to the following, and run hGetChar again, I would expect 'Z' but it's returning 'B':
AZZZZZZZZ

BufferMode is only relevant when writing to a handle, not when reading from it.
From [note Buffered Reading] in GHC.IO.Handle.Types:
Note that the buffering mode (haBufferMode) makes no difference when
reading data into a Handle. When reading, we can always just read all
the data there is available without blocking, decode it into the Char
buffer, and then provide it immediately to the caller.
The documentation for input BufferMode seems to be outdated.

Related

Haskell hGetContents not reading data written with external program

I am opening a file handle with openFile, writing data to it using ByteString.hPutStrLn, and then opening the file in an external editor. I seek the handle back to the beginning of the file with hSeek and expect to read back in the changes made with the external editor. However I am not seeing the changes made in the external editor. Example:
module Main where
import System.Process
import System.IO
import qualified Data.ByteString.Char8 as BS
main :: IO ()
main = do
handle <- openFile "myfile" ReadWriteMode
BS.hPutStrLn handle . BS.pack $ "hello"
hFlush handle
callProcess "vi" ["myfile"]
hSeek handle AbsoluteSeek 0
str' <- BS.hGetContents handle
BS.putStr str'
All that ever gets printed to my terminal is "hello" no matter what changes I make while in vi. However, if I then examine the file, the changes are there. Why isn't hGetContents seeing the changes?
The editor is creating a new file when it saves, and the handle in Haskell is reading from the old file. My editor was going to Vim which has the writebackup feature set by default.
Turning off writebackup results in the behavior I expected, as did using ed.

Why does openFile with WriteMode truncate the file? How not to?

Haskell's openFile has this tidbit in the source:
-- we want to truncate() if this is an open in WriteMode, but only
-- if the target is a RegularFile. ftruncate() fails on special files
-- like /dev/null.
when (iomode == WriteMode && fd_type == RegularFile) $
setSize fD 0
This was very surprising, I assumed that the IOModes were merely wrappers around O_WRONLY, O_RDONLY and O_RDWR. Why is this the case? How does one open a file for write only without truncating?
I think IOMode was designed like the mode argument in the fopen function.
From man fopen:
w - Truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file.
If you wanna to open a file for writing only (without truncate it) then you wanna append it usually. So just use AppendMode. If you don't wanna to have position on the end of a file (also you don't wanna to have position on the begin of a file usually) you can use the hSeek to change it.
IO modes AppendMode or ReadWriteMode should open the file for writing without truncating it.

How to output to terminal but not pipe (or vice versa)

I'd like to display some text to the terminal but not send this text to a piped process. The purpose is to format differently the data that is being displayed (for pretty printing) and that which is being sent to subsequent processes.
For example, I would like, once the executable is called like this:
$ execname | otherproc
to both output something that will be displayed on the terminal and send some string through the pipe to otherproc. Is there something, maybe like a System.IO.Handle such as stdout, that I could use to send data either to be displayed in terminal or to the pipe?
Yes, if you make some assumptions. You can check if it is the console by using the System.Console.Terminal.Size module:
import System.Console.Terminal.Size(size)
isConsole :: IO Bool
isConsole = liftM isJust size
basically what happens is it checks if the terminal has defined a size (so it is Just x). If so we assume it is a terminal, if not we make the assumption that it is a pipe (which has no size). Of course it is up to a console to specify its size properly.
The following seems to be working: send the data that is meant to be piped to the standard output (with putStrLn for example), and the data to be displayed to the file /dev/tty. For example, the following send "a" to both:
import qualified System.IO as SIO
main = do
SIO.withFile "/dev/tty" SIO.WriteMode (\h -> SIO.hPutStrLn h "a")
putStrLn "a"
That way, running the shell command
$ myProg | sed 's/a/A/'
in a terminal displays:
a
A
Only the second A was replaced, i.e. the one send to sed through the pipe with putStrLn, and not the one directly written to /dev/tty.

GHCi and compiled code seem to behave differently

I have a very strange problem. The following code gives different results when compiled as compared to running in ghci,
main = do
putStr "Please enter your name: "
name <- getLine
putStr ("Hello, " ++ name ++ ", how are you?")
When run it in ghci it does as one would expect,
Please enter your name: dglmoore
Hello, dglmoore, how are you?
However, when I compile the code to an executable it requires that I provide the input before any output is generated so I end up with this,
dglmoore
Please enter your name: Hello, dglmoore, how are you?
I've seen a similar problem before, but I cannot seem to find it again.
I am using ghc version 7.4.1 from the Haskell Platform version 2012.2.0.0.
Anyone have any idea why they give different results and how I can get both versions to do the "correct" thing?
It's a buffering issue. Usually IO is line buffered (i.e. the output doesn't actually show up on the screen until you print a new line or exceed the buffer size) unless you explicitly flush the buffer. In ghci it isn't, so the issue doesn't show up.
You can use hFlush stdout to flush stdout, causing the output to be printed to the screen, before you call getLine.
Alternatively you can use hSetBuffering NoBuffering to disable buffering altogether, removing the need for hFlush. That could have a negative impact on IO performance though.

How can I get input right when it is typed in Haskell?

I have written a Brainfuck interpreter in Haskell, but it only operates on the input once I hit Ctrl-D to signal EOF. How can I make the program act on each character when it is typed?
Here is the source. To use the program, give a file to interpret as an argument or type your program in the first line of stdin.
It sounds like your input is being buffered. You can modify the buffering mode of a file handle with System.IO.hSetBuffering. If you are reading from standard input, for instance, then you could disable buffering with:
import System.IO
hSetBuffering stdin NoBuffering
getLine waits for a newline character to be typed (\n), because what if the user typed a bunch of characters, but never pressed enter? Then it would be an error if some of the "line" had already be processed, if that "line" wasn't a line after all.
You should use getContents instead which will return everything that is typed at the terminal.
Also, you are using the following line:
then hGetContents =<< openFile (head args) ReadMode
This will open a file and never close it. This is fine for your short program, but it might be a better idea for the future to get used to doing this:
then readFile $ head args

Resources