It can, but only at the cost of complecting source and sink.
By default, function composition is eager: a function is expected to do its work, and hand the whole return value off to the next function.
We can make this lazy, by setting up an iterator and handing this off. At some expense: our next function must expect an iterator, and therefore can't handle a full data structure. At minimum it must coerce those into an iterator when encountered.
Also, it gets awkward to reason about iterators wrapped in iterators wrapped in iterators, even with a debugger, you get action at a distance, where the fourth function in your thread is failing because the first iterator of three has a flaw in it.
Shell pipes handle all of this for the user, with sensible defaults which can be overridden and modified for special cases. It's a powerful abstraction and I wish more languages offered something like it.
By default, function composition is eager: a function is expected to do its work, and hand the whole return value off to the next function.
We can make this lazy, by setting up an iterator and handing this off. At some expense: our next function must expect an iterator, and therefore can't handle a full data structure. At minimum it must coerce those into an iterator when encountered.
Also, it gets awkward to reason about iterators wrapped in iterators wrapped in iterators, even with a debugger, you get action at a distance, where the fourth function in your thread is failing because the first iterator of three has a flaw in it.
Shell pipes handle all of this for the user, with sensible defaults which can be overridden and modified for special cases. It's a powerful abstraction and I wish more languages offered something like it.