POP. One of the great things by Unix is that everything is a stream: files, sockets, keyboards, etc. Most file I/O libraries try to use this unifying concept. In addition, most languages have the notion of an iterator or enumerator. Mantra combines the 2 into the formal semantics of the language. (For some piping madness see How to look like a UNIX guru
)
The following UNIX commandline illustrates a simple pipeline:
cat log | grep '/index.html' | wc -l
The is one of my favorite patterns; it gives you a histogram of the elements in the first column of file log:
cat log | awk '{print $1;}' | sort |uniq -c|sort -r -n
UNIX launches each one of the commands as a separate process and hooks up the output of the left operand to the input of the right operand. When all the processes have terminated, the command finishes. The simple input / output stream concept supports an astonishing amount of code reuse because you can put together these things in really interesting combinations.
I have formalized this notion into a stream of objects in Mantra. Every object has an in and out field, which are hooked up to standard input and standard output by default. The pipe operator does as UNIX does and changes these fields to hook together multiple objects. The pipe operator also launches a thread for each operand of a pipe and calls the predefined main() on each object. Essentially then you would call these objects actors.
The significance of POP lies in that you can create new functionality by piping actors together rather than adding methods to classes. For example, imagine providing a facility for converting a file to a stream of lines. The traditional way is to create a lines() method and then call f.lines(). You could also add f.words() and so on. But, the exact same functionality is useful for strings and any other stream of characters.
Pipelines offer a better solution. Create a single input/output processor (an actor) and then pipe an input source to the actor. For example, the following code prints the lines and words from file f and also string s without having to add methods to File and string:
f => Lines();
f => Words();
s => Lines();
s => Words();
In the future, when you discover a need to convert socket input to a series of lines, you can simply connect the socket to a Lines actor:
http = serverSocket.accept();
http => Lines() => { string line | ...} ;
More importantly, you might not have source code for a library, which would prevent the addition of new methods. That is, you may not have source for Socket and, thus, you could not add method lines; http.lines() is not an option.
The first and last operands of a pipeline are converted to input and output object streams if possible, but many combinations are possible. For example, sometimes the first operand is an actor that generates output rather a stream that, say, pulls data from a file. Similarly, the final operand could be an actor that just consumes data and emits a result to stdout.
The automatic conversion of first and last operand to streams provides a very terse way to perform a file copy (streams automatically open/close in a pipeline):
Mantra closures with a single parameter and a return value are also
actors:
f2 = File("a");
f3 = File("b");
f2 => {char x|return x;} => f3;
Method names are also converted to actors:
f = File("coffee");
f => Lines() => println;
In summary, POP encourages programmers to concentrate common functionality in a library of actors that can be easily combined without altering existing classes. Method call f.lines() becomes f => Lines(), a pipe between two actors.