Search code examples
functionprogramming-languagespostfix-operator

Why aren't unary functions usable in postfix notation?


I see those two main advantages to postfix over prefix notations for unary functions:

  • nice formatting, no nesting
  • maps better the 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.


Solution

  • 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.