logoalt Hacker News

amelius • today at 12:55 PM • 16 replies • view on HN

I want to put the called function after the arguments:

    (((x)f)g)h
So I can see exactly the order in which things happen, from left to right. As opposed to:

    f(g(h(x)))
where I have to read backwards.

Replies

bunderbunder • today at 3:33 PM

One of the things I love about programming in dialects of ML like ocaml and f# is the forward pipe operator:

  x
  |> f
  |> g
  |> h

It's nice because it just puts whatever's on the left of the operator in as the last argument. So it also works for functions with an arity of more than one.

So if it were something like

  h(z, g(y, f(x))
you can write it as

  x
  |> f
  |> g y
  |> h z

It works really nicely for data munging code:

  my_data
  |> filter (x => x > 37)
  |> select (x => x * 2)
  |> sum
but unlike doing it with method calls in object oriented code, inserting a new function into the chain doesn't involve modifying a class or or mucking around with more complex language features like extension methods and traits.
➕ show 1 reply
ktpsns • today at 1:08 PM

Mathematica has a crazy amount of syntax abbreviations (https://reference.wolfram.com/language/guide/Syntax.html). Of course your whish is fulfilled there: x // f called the postfix operator.

taolson • today at 4:34 PM

In a lazy language like Haskell, the order in which things are actually evaluated is f -> g -> h -> x, because arguments to a function are only evaluated when the are needed, rather than before they are called. But even so, I still prefer writing it as x |> h |> g |> f.

danlitt • today at 1:03 PM

Do you mean (((x)h)g)f ?

since the order in which things happen is "h first, then g, then f"

mesrik • today at 2:41 PM

So would you like to write shell commands connected with pipes like that too?

So is this better too

  $ less | find / -type f -perm /6000 -ls
than ?

  $ find / -type f -perm /6000 -ls | less
Just because shell actually starts executables right to left when using pipes. Reason to this behaviour is because it can fail early if there is problem constructing pipe first and not spend lot of time something finding later that some pipe components can't start and receive data properly.

ps. I'm fine with current notation and would not like change it.

➕ show 1 reply
adverbly • today at 1:41 PM

That actually makes so much sense...

When you're calling a function, you're putting some things into the function... You have to get the things first before you can put them in...

Why didn't it evolve like that in the first place? Is it a byproduct of mathematical notation? If so, I wonder why mathematical notation evolved that way in the first place...

➕ show 2 replies
nine_k • today at 1:33 PM

Did you even have an RPN-based calculator at school?

As to more serious programming languages, Erlang, F#, Haskell have forms of a "pipe" operator, roughly

  [1 2 3] |> sum |> print
For it to work properly, functions should be either functions of one argument, or support currying.

But the original algebra functions, as introduced by Leibniz in 17th century, were, of course, pure functions, more like properties than procedures. So cos(x), aka cos x, is just a formalized shorthand for "cosine of x", so it preserved the order of words typical for Germanic and Romance languages. Maybe if functions were introduced by the Japanese, we would have the reverse order, kind of x の cos.

➕ show 2 replies
ashkankiani • today at 2:00 PM

Stack based languages and the APL tradition all follow this pattern, which I agree can be more terse and nice too. I've also been experimenting with right-associative calls which does the same thing but in reverse. For a data pipeline you're more likely to transform from A->B, so I think the post-fix syntax makes sense.

But you can also define a "thread" operator easily for doing subsequent function calls. ala `threaded x { f; g; h }`

skocznymroczny • today at 2:08 PM

Assuming you mean h)g)f, you can do it in D. D allows you to write this:

f(g(h(x))) as x.h().g().f() or even x.h.g.f (parenthesis are optional).

layer8 • today at 1:14 PM

If `f` is an expression, that would require it to be evaluated last as well.

account42 • today at 1:17 PM

This is why some languages have the | pipe syntax for chaining operations.

raverbashing • today at 1:31 PM

And that's why "purely procedural" languages don't make much sense to me

It's more natural to write

    x.h().g().f()
kiicia • today at 2:42 PM

you can start with

    var chain = x=>f=>f?chain(f(x)):x;
    chain(x)(h)(g)(f)();
if you need to partly curry to free first argument

    var _ = (f,...y)=>x=>f(x,...y);
    var sum = (x,y)=>x+y;
    chain(1)(_(sum,1))()
if you want whole pipeline

    var $ = (x,...f)=>f.reduce((x,f)=>f(x),x);
    $(1,_(sum,1));
which gives fun ability to extract function without input param

    var sum1 = _($,_(sum,1));
    sum1(1);
... which in turn is just having fun with point-free JS
anal_reactor • today at 2:10 PM

I want to put the function below the arguments, so that the call tree actually looks like a tree

    u v x y
     f   g
       h
This is h(f(u,v), g(x,y)).
➕ show 1 reply
psychoslave • today at 1:22 PM

[flagged]

Rohansi • today at 1:22 PM

The function reference is actually evaluated first so your preferred order is actually backwards! Consider this:

  foo.bar(x)
The `foo.bar` field has to be retrieved before the function can be called. It does not evaluate `x` first. That is observable with JS proxies, or you can test with code like this:

  foo()(bar())
➕ show 1 reply