Executables are like functions, passing arguments to a command is like partially applying arguments to a function, standard input is a final argument, and standard output is a return value.
Piping from one program to another is like function composition.
You could also think of pipelines as monadic composition instead, either with an I/O monad (to enable each program in the pipe to do I/O) or state monad (if you think of the data as it moves through the pipe as a thing which each step of the pipe modifies) - this is a fine and correct view, but the I/O "side effects" of a pipe are just an implementation detail, and no data is being mutated unless the system can optimize it so that the same memory is reused for multiple pipes.
Executables implementing subcommands or options that change behavior are like modules of functions, but that's a little less interesting.
Just a neat little bit of perspective, connecting dots, showing the sameness of logical shape between what we normally think of as very different things.














