I see those two main advantages to postfix over prefix notations for unary functions:
Input -> Function -> Ouput
way of thinking about data processing. Here is an example:
def plus2(v: Int) = v + 2
def double(v: Int) = v * 2
def square(v: Int) = v * v
// prefix
square(
double(
plus2(1)
)
)
// postfix
1.plus2
.double
.square
This can be emulated as in the Java stream api with method chaining or other user techniques. But I'm not familiar with any programming language that offers first-class support for postfix function application.
What are the programming language design reasons not to offer first-class postfix notation support for unary functions? It seems trivial to support.
I'm not familiar with any programming language that allows this.
Your example syntax is very similar to method chaining. In object-oriented languages, a method with no arguments is effectively a unary operator on whatever type the method is declared on. So here's your computation in Java, with the same declarations, in postfix order:
class Example {
final int x;
Example(int x) { this.x = x; }
Example add2() { return new Example(x + 2); }
Example mult2() { return new Example(x * 2); }
Example square() { return new Example(x * x); }
public static void main(String[] args) {
Example result =
new Example(1)
.add2()
.mult2()
.square();
}
}
You need the brackets ()
to call them, of course, but it's still postfix order. Unfortunately, you can't adapt this to use static methods instead of instance methods, at least not without abusing Optional
or Stream
like this:
Optional.of(1)
.map(StaticExample::add2)
.map(StaticExample::mult2)
.map(StaticExample::square)
.get()
I guess the reason OOP languages don't make it easier to use static methods this way is because it would be strange to have special syntax which privileges static methods over instance methods. The point of OOP is to do things with instances and polymorphism.
It's also possible in functional languages, using functions instead of methods: here's F#:
let add2 x = x * 2
let double x = x * 2
let square x = x * x
let result =
1
|> add2
|> double
|> square
Here the forward pipe operator |>
serves a different semantic role to the .
in Java, but the effect is the same: the functions are written in postfix order.
I guess the main reason that the forward pipe operator only tends to exist in functional languages is because partial application allows you to write a pipeline-style computation using non-unary functions. For example:
nums
|> List.filter isEven
|> List.map square
Here, List.filter
and List.map
take two arguments, but if you call them with one argument then they return a unary function. Non-functional languages tend not to have partial application (at least not so easily), so a forward pipe operator would be less useful.
There is also the less-well-known concatenative programming paradigm, where everything is naturally done in postfix order, with no extra syntax required. Here's my own toy language named fffff:
(2 +) >!add2
(2 *) >!double
(@ *) >!square
1 add2 double square >result
Here, even the assignments like >result
are done in postfix order.