Reputation: 4128
I was reading: Haskell interact function
So I tried
interact (unlines . map (show . length) . lines)
And it worked as I expected. I type something, press enter, then I get the length printed at the prompt.
So then I wanted to try make it simply repeat what I typed, so I tried
interact (unlines . map id . lines)
But now it repeats every character I type in. Why is that? I thought the trick was in the lines
followed by unlines
- but it's clearly not. lines "a"
produces ["a"]
, so how come in the first function when I start typing my input, it doesn't just immediately give "1" as the output? There's clearly something I misunderstand about "Finding the length of a string is not like this -- the whole string must be known before any output can be produced."
Upvotes: 2
Views: 334
Reputation: 11923
This is to do with lazy evaluation. I'll try to explain this in as intuitive a way as possible.
When you write interact (unlines . map (show . length) . lines)
, every time a character is input, we don't actually know what the next output character can be until you press enter. So, you get the behaviour you expected.
However, at every point in interact (unlines . map id . lines) = interact id
, every time you enter a character, it's guaranteed that that character is included in the output. So, if you input a character, that character is also output immediately.
This is one of the reasons that the word "lazy" is a bit of a misnomer. It's true that Haskell will only evaluate something when it needs to, but the flipside of that is that when it needs to, it'll do so as soon as possible. Here Haskell needs to evaluate the output since you want to print it, so it evaluates it as much as it can—one character at a time—ironically making it seem eager!
More specifically, interact
isn't intended for real time user input—it's intended for file input, in which you pipe a file into an executable with bash. It should be run something like this:
$ runhaskell Interactor.hs < my_big_file.txt > list_of_lengths.txt
If you want line-by-line buffering, you'll probably have to do it manually, unless you want to 'trick' the compiler as Willem does. Here's some very simple code that works as you expect—but note that it has no exit state unlike interact
, which will terminate at the EOF.
main = do
ln <- getLine -- Buffers until you press enter
putStrLn ln -- Print the line we just got
main -- Loop forever
Upvotes: 3
Reputation: 477607
The fact that lines "a"
produces ["a"]
does not mean that if you are currently entering a
, that lines
just processes the input to a list ["a"]
. You should see the input as a (possibly) infinite list of characters. In case the prompt is waiting for user input, it is thus "blocking" on the next input.
That however does not mean that functions like lines
can not partially resolve the result already. lines
has been implemented in a lazy manner such that it processes the stream of characters, and each time when it sees a new line character, it starts emitting the next element. This thus means that lines
could process an infinite sequence of characters into an infinite list of lines.
If you use length :: Foldable f => f a -> Int
however, then this requires the list to be evaluated (not the elements of the list however). So that means length
will only emit an answer from the moment lines
starts emitting the next item.
You can use seq
(and variants) to force the evaluation of a term before a certain action is done. For example seq :: a -> b -> b
will evaluate the first parameter to Weak Head Normal Form (WHNF), and then return the second parameter.
Based on seq
, other functions have been constructed, like seqList :: [a] -> [a]
in the Data.Lists
module of the lists
package.
We can use this to postpone evaluation until the first line is known, like:
-- will echo full lines
import Data.Lists(seqList)
interact (unlines . map (\x -> seqList x x) . lines)
Upvotes: 3