Julia: [RFC] Support Uniform Function Call Syntax

Created on 20 Apr 2019  Â·  18Comments  Â·  Source: JuliaLang/julia

Since Julia is struct oriented, this could be a nice way make the language more ergonomic for methods
https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax

Assume

struct Blah
    ...
end

foo(b::Blah,i::Int32)
      ...
end

b = Blah(..)

In addition to this syntax

foo(b,123)

this would also be acceptable if a function signature existed with Blah as first parameter type

b.foo(123)

Edited: fixed function name to be idiomatic

Most helpful comment

Yes, you can write [-1,2,3] |> y->map(x -> x^2,y) |> y->map(x -> x^2,y) |> y->map(x -> x^2,y) in an ugly way, but you could also just write [-1,2,3] .|> x->x^2 .|> x->x^2 .|> x->x^2 (or [-1,2,3].^8) — we have plenty of other ways to construct this in Julia.

I think the proposal with the most support (#24990) has been to implement nicer multi-argument function chaining in the future with something more flexible like the Scala-like syntax [-1,2,3] |> map(_^2,_) |> map(_^2,_) |> map(_^2,_).

Furthermore, the proposed "uniform function call syntax" wouldn't even solve this, because it would transform [-1, 2, 3].map(x -> x^2) to map([-1, 2, 3], x -> x^2), which is the wrong argument order for Julia.

Julia isn't an OO language. The first argument isn't special for dispatch, so OO-like syntax here is a non-starter.

All 18 comments

This goes against Julia's functional philosophy and threatens to make Julia into an OO language, so I don't think you're going to get much agreement from experienced Julia developers.

Also, note that it'd be more idiomatic to just define:

function foo(b::Blah, i::Int)
...
end

foo(b, i)

No need to name it Blah_foo, since Julia uses multiple dispatch. Now foo can be extended to be called with instances of Bar or Flub or whatever as the first argument by just adding another method.

To your point, I consider this to be quite ergonomic.

I don't see how a new way to call a function threatens a whole language to become OOP. That's a slippery slope fallacy. People are writing functions associated with structs right now and that's not a threat to the language.

Nim is a procedural language that takes this idea pretty far and it is very imperative oriented: https://narimiran.github.io/nim-basics/#_calling_the_procedures

Another benefit I see to uniform function call syntax is the support of autocomplete in IDEs/REPL for Julia. One would simply suggest a list of functions with the variable's type as a first parameter.

Let's put autocomplete aside for a moment, since it's mostly orthogonal to your RFC. Let's also not discuss just ergonomics since that is mostly a matter of personal choice, and UFCS doesn't actually shorten the number of keys you have to press to achieve effectively the same behavior with idiomatic Julia code (as I demonstrated above).

What concrete benefits do you see from adding UFCS to Julia? What things are currently difficult/infeasible to do with idiomatic Julia syntax that UFCS would improve on? If you can, try to refrain from pointing to other languages as the core of your example, and instead focus on what people use Julia for today, and how UFCS can help them. Specific examples would be very helpful as well!

If you can illustrate how UFCS really is a game-changer or significant improvement over what's currently possible, without any significant downsides to implementing it, maintaining it, and seeing its usage become commonplace in the package ecosystem, then I think you'll be able to garner support and valuable discussion for your RFC.

The basic problem with allowing foo(b,123) to be called as b.foo(123) is that it implies that the first argument is special. This is true in OO languages because the first argument is used for dispatch, but is mostly not true in Julia because multiple dispatch treats all arguments equally.

isn't this a duplicate issue of #25052?

It solves problem of function chains natively

[-1, 2, 3].filter(x -> x > 0).map(x -> x^2)

instead of

map(x -> x^2, filter(x -> x > 0, [-1, 2, 3])).

And it has absolutely nothing with object oriented stuff.

And yes - it's much better to use in IDE with autocompletion.

Why is this issue closed?

Because aside from tab completion there are nothing but negatives to it? In general we don't make changes that would make stuff worse.

there are nothing but negatives to it

@timholy so you consider this

map(x -> x^2, map(x -> x^2, map(x -> x^2, [-1, 2, 3])))

as being superior to

[-1, 2, 3].map(x -> x^2).map(x -> x^2).map(x -> x^2)

Right? With multi-line it will be even more interesting.

@alexeypetrushin, you can already use

[-1,2,3] .|> x->x^2 .|> x->x^2 .|> x->x^2

(and maybe [-1,2,3] .|> _^2 .|> _^2 .|> _^2 in the future #24990) for map-like function chaining.

We also have

((([-1,2,3].^2).^2).^2)

These have the additional advantage over chaining map in that they both fuse into a single loop with no temporary arrays.

And you can also chain map directly:

[-1,2,3] |> y->map(x -> x^2,y) |> y->map(x -> x^2,y) |> y->map(x -> x^2,y)

@stevengj try use pipes for functions with many arguments, don't know how things are now, but previously julia pipe had limitations.

This last line [-1,2,3] |> y->map(x -> x^2,y) |> y->map(x -> x^2,y) |> y->map(x -> x^2,y) - you basically doing exactly what uniform function call does, but in an verbose and strange way.

Being able to write data processing pipelines from left to right is a happy accident that x.f(y) method call syntax happens to be good for. That syntax already has a meaning in Julia so there's no way we're changing the meaning of it in any 1.x release (and pretty unlikely in 2.0, to be honest). The most constructive direction this conversation can take is thinking of alternative ways to get the benefits of the left-to-right processing flow. Insisting that one wants the [-1, 2, 3].map(x -> x^2).map(x -> x^2).map(x -> x^2) syntax to work as is in Julia is not going be productive.

Yes, you can write [-1,2,3] |> y->map(x -> x^2,y) |> y->map(x -> x^2,y) |> y->map(x -> x^2,y) in an ugly way, but you could also just write [-1,2,3] .|> x->x^2 .|> x->x^2 .|> x->x^2 (or [-1,2,3].^8) — we have plenty of other ways to construct this in Julia.

I think the proposal with the most support (#24990) has been to implement nicer multi-argument function chaining in the future with something more flexible like the Scala-like syntax [-1,2,3] |> map(_^2,_) |> map(_^2,_) |> map(_^2,_).

Furthermore, the proposed "uniform function call syntax" wouldn't even solve this, because it would transform [-1, 2, 3].map(x -> x^2) to map([-1, 2, 3], x -> x^2), which is the wrong argument order for Julia.

Julia isn't an OO language. The first argument isn't special for dispatch, so OO-like syntax here is a non-starter.

Why is this issue closed?

is answered by

That syntax already has a meaning in Julia so there's no way we're changing the meaning of it in any 1.x release (and pretty unlikely in 2.0, to be honest).

And it is a valid answer.

The first argument isn't special for dispatch

the first elements in a list is neither special so head or first or car should be deleted from any language. I am fine with it.

Asides from this, I am start recognizing the community being toxic and fundamentalism.

Cool reaction, dude. Devs take the time to explain why something isn't a good fit for this language. You call them toxic for explaining it to you. There's nothing "fundamentalist" about this. If you're going to engage here, at least engage with what someone has actually written. The first argument is totally non-sequitur: it's common to want to take the first element from a collection, so there is a first function. It's also common to want to take the last element, so there is a last function. What in the world does that have to do with method/function call syntax?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

sbromberger picture sbromberger  Â·  3Comments

yurivish picture yurivish  Â·  3Comments

helgee picture helgee  Â·  3Comments

omus picture omus  Â·  3Comments

wilburtownsend picture wilburtownsend  Â·  3Comments