I ran into an issue where I expected min.(1,f) to be lowered to broadcast(min,1,f), but instead it is lowered to broadcast(x->min(1,x),f). There are a few issues with this behaviour:
It breaks the “semantic” contract of broadcasting.
Every constant needs to recompile.
In my case, f is a Fun, and I want to override broadcast(::typeof(min),::Number,::Fun) to calculate break points. This override is not called with the current behaviour. (The current approach of overriding min(::Number,::Fun) works, but it’s breaking the expected behaviour of min to return one of the arguments.)
Is there an argument in favour of the current approach? Or should I create an Issue on GitHub?
Inlining literals is significantly faster than passing them as broadcast arguments. Because of fusion, overloading broadcast(::typeof(min), ...) is not very useful anyway since your method won’t be called as soon as someone combines min with another dot call. e.g. your function wouldn’t be called for min.(x, f.^2) either.
No recompilation will happen because it doesn’t redefine anything. It needs to be compiled as many time as it appears in the code which is consistent with everything else.
The difference is supposed to be invisible and any method definition that breaks this is invalid.
Also, if you are defining a method that does not support any code transformation done by the dot syntax, it’s a clear sign that it should just be defined as a normal function instead.
No recompilation will happen because it doesn’t redefine anything. It needs to be compiled as many time as it appears in the code which is consistent with everything else.
One needs to be able to override broadcast to use generic code (e.g., DifferentialEquations.jl).
Besides, it wasn’t my idea to override broadcast, I just merged a pull request from @stevengj
My point is less about this particular usage, but rather this sort of optimization is morally equivalent to compiler optimizations (a la Matlab), where the speed and behaviour of min.(1,f) is inconsistent with the implied lowering to broadcast(min,1,f).
That doc can be clarified for it excludes fusing.
In general, the lowering should be allowed to do any transformation that’s valid for the base broadcast implementation.
More generally, f.(args…) is actually equivalent to broadcast(f, args…)
That relation implies (and the underlying implementation assumes) that f is defined for the element types (or type if not with a size) of the container-like objects in args.
Thinking about this a bit more, there might be a way to make the literal transformation after parsing, so if someone overwrites broadcast(::typeof(f), ...) it will get called when no dot-fusion occurs.
The only problem is that it can prevent that a broadcast call with a lot of arguments gets inferred (given the restrictions to avoid handling potentially infinite types). But this is a already a problem anyway.
Yes that’s what’s implemented. But it doesn’t work for functions that require break points, like abs.(x) or min.(1,x).
The latter example is challenging because the documentation for min says it should return one of the inputs, so the current pointwise implementation of min(1,f) is possibly confusing.