My understanding is that you have to consider separately 1) the device, 2) the data stream interface in general, 3) the line reader-writer interface in particular, 4) the semantic.
stdout
is a technical endpoint private to your process and with the same lifecycle, to write byte stream, not to read. You can duplicate a reference to it so it is referred to by another name, its capability is always output. It is created and deleted with the process. stdin
is an equivalent on which you read byte stream. Its capability is always input. Depending on underlying device, they may have both capabilities, but the standard and reliable model is agnostic about that.
A byte stream is a general interface that accept two signals on its write-end: put
, close
and emits two corresponding signals on its read-end: byte
, EOD
. When the write-end (e.g stdout
) receive put
, the read-end has to get a byte
. When the write-end accept close
, the read-end get EOD
. EOD
(close
) is sent either explicitly when the stdout
descriptor is closed by exec >&-
or when the process terminates.
A line reader or writer is a special way to use a byte stream, with an additional signal newline
, but it is not inherent to stdin
nor stdout
, it is just data transformed to and from text lines because it is convenient, maniable and robust. This is a line protocol or discipline, historic standard, iterable and reasonably agnostic. The newline
signal is a convention ; whatever it may be, it has to take one value. There is no alternative unless relying on another state but the device has only two states, opened or closed. In other words, the byte stream is device driven while the line reader is data driven.
Given that, supposing you want to communicate between two processes via a pipe, the minimal signal the speaker can send to the listener is byte
and EOD
, but this can't be done by iteration as you would do with text lines because once closed (EOD
sent), the file descriptor is not reusable (even while it may be hackable). But, as long as you don't need to iterate, you can rely on EOD
.
Then, let's be realistic : polymorphism as you say, is not a matter of raw data in a byte stream, if you lose the iterable line model in doing that. Polymorphism is a matter of model on top of a simple iterable protocol, say line discipline, on top of a raw protocol, say byte stream, that can be implemented on socket, pipe, file, tape, printer or whatever.
However, on top of the stream model, there are only two iteration controls : either you know in advance how much to read, or you read one by one until a signal. Knowing that, you can adopt the discipline you prefer on top of the raw stream instead of the text lines, it is always a matter of parser. A parser is a machine that read a stream and builds other objects in its own language model.
And, to be an acceptable interface on top of a stream, it has to resume parsing at the exact point where the last iteration left it, sequentially and forward.
Now that we know that polymorphism + iteration => sequential parser, we can come back to standard and be very satisfied. What we need is Keep It Simple Stupid.