Argument-oriented dot syntax?

Here’s an annoying question: wouldn’t things be easier if the dot-syntax for broadcasting focused on arguments rather than functions?

An example might explain the question better. Assume I have a function lerp(x::Vector, y::Vector, xx::Number) which takes in data points x,y and evaluates the linear interpolant at point xx. Soon enough I will want to evaluate the interpolant at a series of points xx:Vector, but lerp.(x,y,xx) won’t work because broadcast tries to zip the three vectors together. Instead, I have to use work-arounds like lerp.((x,),(y,),xx) or (xx->lerp(x,y,xx)).(xx), both of which are a bit annoying. Imagine if I could write this as lerp(x,y,.xx) where the dot in front of xx indicates that I only want to broadcast over xx! This seems more natural and can easily be made backwards compatible by introducing the convention f.(x,y) === f(.x,.y).

2 Likes

I love the proposed syntax, but in terms of functionality, I though that Ref is what you want right now:

lerp.(Ref(x), Ref(y), xx)

I.e. you specify which arguments to freeze rather than broadcast.

3 Likes

I discover recently the power of the broadcast function (the function behind the dot syntax); the problem can be expresed like this:

result= broadcast(z->lerp(x,y,z),xx)

is more verbose, but it makes clear what is constant and what is broadcasted

The way I think about broadcast is that it is all about functions — eg broadcast fusion. Not wanting to broadcast something is the special case, for which we have Ref & friends.

You could, of course, propose a syntax where

f(.x, .y, z)

is equivalent to the current

f.(x, y, Ref(z))

but you might as well just wait for sugar like &x that would make escaping much more compact (not that I am bothered about Ref that much, though, which is I guess why this PR was not rushed through).

2 Likes

Maybe there’s space for both? It seems sometimes it’s more convenient to exclude arguments from broadcasting, sometimes it’s more convenient to include them.

Here’s another argument in favour of having both syntaxes: nested broadcasting. If I have a vector of vectors v and want to shift all of them by a constant c, I could write ..v + c. Currently there’s no easy way to do this, afaik. I admit that currently this is a bit of a stretch of the concept, though.

If we also have a “don’t-broadcast” operator like Ref()/&, you could even take this a notch further and let ..v + .&w denote something like broadcast((vi,wi)->broadcast(vii->vii+wi,vi),v,w), while ..v + &.w denotes broadcast(vi->broadcast((vii,wi)->vii+wi,vi,w),v).

IMO functions with a too many (> 3) arguments are code smell anyway, and if you find that you are excluding more arguments from broadcasting thnn you include maybe some refactoring is in order.

Eg if I end up doing

f.(x, Ref(y), z, Ref(w), u, Ref(parameters), Ref(κ))

then maybe I would benefit from

f(.x, y, .z, w, .u, parameters, κ)

but perhaps it would be better to take a coffee break, and refactor this into

f(Ref(CollectedParams(y, w, parameters, κ)), x, z, u)

You are thinking of Package development where you may have a point (I’m unconvinced) but Julia is not just for people like you who like to spend a lot of time structuring and fine-tuning their codes.

Good coding style benefits the programmer and the reader (may or may not coincide) in all contexts. When practiced regularly, it becomes a habit and takes less and less extra effort.

Too many function arguments are generally recognized as code smell. In an OO context, Robert C Martin (Clean Code, Chapter 3) writes:

The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification—and then shouldn’t be used anyway.

but in Julia you get an extra argument because the above does not count the object.

In Julia, nothing precludes good code organization in scripts, because you essentially get the same tools for it as packages. Also, trivial scripts have a tendency to balloon into 2k-LOC monsters over time.

1 Like