It isn't efficient. We've had compositional tools for ages with good old unix pipes and similar; you can build those into a kind of hierarchical namespace with virtual filesystems, like a bunch of tools similar to this one, and view things through some chain of filesystem interfaces to implicitly perform operations on them. But these methods of composition are very opaque to the kernel that connects their components (FUSE is even slower than this design contraint implies because of how many times you go back and forth between the kernel and userspace), and as such you have lots of redundant copies of buffers, serializations/deserializations, and so on, which destroy your performance compared to a big monolithic "blob" program with built-in specialized processing pipelines.
The real direction to look is for systems where your tools are composable but also which have novel means of performing whole-pipeline optimization--like stream fusion in haskell, or a JIT'd Lisp machine. Of course, those examples are far from perfect, but they show some attributes of what partial solutions to the age-old problem might look like. They do have their own problems as well, like ad-hoc data processing in haskell being more thought-intensive (though, you know, correct in processing edge cases) compared to composed string filters in a shell.
You could take a job composition system like unix pipes, and add a buffer discipline that allows for zero-copy, or provide a way to declare buffering behavior, but at that point you're essentially building a typesystem on top of some real-world semantics, and it's a better idea to start from what you want to achieve in the first place and then design upward from there. If you start from the bottom up, you can also merge the operations performed inside each (memoizing pure work or fusing stream operations) or describe what kind of data you accept and produce, both of which can help avoid redundant computation. You'd probably end up with something with affine or linear types, a module system a la ML, and some novel combination of other handy features for ergonomics (which is largely what has kept unix pipes relevant to this day).
Presumably, if this were to catch on, it would start in toy projects and projects with a major usability advantage, where the performance loss is acceptable, and as a secondary means of using other programs. Then, we might see optimizations to make the performance of this method not horrible.
In concept, it should be relatively simple to recognize where a FUSE filesystem is mounting another FUSE filesystem and bypass the kernel to let the two processes talk directly to each other. You still have the problem that any operation would need to travel through N processes. You might be able to improve on this with a mechanism for a filesystem to say "I have to effect on operation X" and have it be bypassed when X occurs.
Correct me if I'm wrong, but I would've assumed that most methods of IPC (unix sockets, pipes...everything except shared memory) would need to be copied into kernel space (via a write(2) then copied back out (via a read(2)) at some point. Are you suggesting that FUSE add a mechanism to open some shared memory communication path between FUSE processes?
The real direction to look is for systems where your tools are composable but also which have novel means of performing whole-pipeline optimization--like stream fusion in haskell, or a JIT'd Lisp machine. Of course, those examples are far from perfect, but they show some attributes of what partial solutions to the age-old problem might look like. They do have their own problems as well, like ad-hoc data processing in haskell being more thought-intensive (though, you know, correct in processing edge cases) compared to composed string filters in a shell.
You could take a job composition system like unix pipes, and add a buffer discipline that allows for zero-copy, or provide a way to declare buffering behavior, but at that point you're essentially building a typesystem on top of some real-world semantics, and it's a better idea to start from what you want to achieve in the first place and then design upward from there. If you start from the bottom up, you can also merge the operations performed inside each (memoizing pure work or fusing stream operations) or describe what kind of data you accept and produce, both of which can help avoid redundant computation. You'd probably end up with something with affine or linear types, a module system a la ML, and some novel combination of other handy features for ergonomics (which is largely what has kept unix pipes relevant to this day).