julia
julia copied to clipboard
Allow function negation `-f` to work like `!f`
We have !f === (!)∘f to negate any boolean function. Was there ever discussion of making -f === (-)∘f too? Both are small conveniences to avoid defining x -> op(x). This one would allow for example:
julia> A = reshape(1:16,4,4);
julia> eigvals(A; sortby=-real) # does not accept rev=true
4-element Vector{ComplexF64}:
36.209372712298546 + 0.0im
-4.146487327145629e-17 + 1.9341169324764503e-16im
-4.146487327145629e-17 - 1.9341169324764503e-16im
-2.209372712298546 + 0.0im
julia> sum(-log, A; dims=1) # -sum allocates twice
1×4 Matrix{Float64}:
-3.17805 -7.42655 -9.38261 -10.6846
This will not change the fact that -f.(xs) means -(f.(xs)), when usually fused @. -f(xs) would be better. (Unless you write (-f).(xs) by hand.)
Needs tests obviously, but RFC?
I'm not necessarily opposed to this but I'll raise some downsides.
(1) Defining functionality for functions that doesn't work on general callables means we can't use it in functions that take generic callable argument.
(2) Related, -a is already syntax for AbstractArray while !a isn't, so a callable AbstractArray might have confusing behavior if the -a is different under function application versus indexing. I don't know of any examples like that but there might be some.
(3) I don't like special case syntax: I think it would be better if instead of adding !f and -f to avoid the extra characters in (!)∘f, we could have instead !∘f and -∘f -- without the parentheses -- that would just work and be almost as concise as the special cases !f and -f.
Do the same for +, out of symmetry?
personally speaking I don't think I'm a fan of this. the ("wat" potential) : (cuteness of syntax gained) ratio is too high
Point (2) above is an interesting deviation from how !f works. It's true that ![true, false] is an error, while -[1,2,3] is the same as broadcasting. So it would be somewhat strange if (-a)(i) != -(a(i))... assuming that (-a) were defined like this PR, and that the array is callable.
The only callable array I can think of is from my package, which uses (ab)uses round brackets to mean another kind of indexing. In this case, the two meanings would agree, since indexing the negated array -A is the same as indexing and then negating:
julia> A = AxisKeys.KeyedArray([10, 20, 30], char='a':'c');
julia> A('b') == A[2] == 20 # 2nd element can be accessed by its label 'b'
true
julia> -A('b') == ((-)∘A)('b') == -20
true
The closest related PR is probably #39042, which makes f^2 === f∘f. That has a different overlap of ideas with callable arrays, in that A^2 == A*A for matrices. Note that if both PRs were merged, the parser thinks the minus is outside: -3^2 == -9.
I can't picture why defining +f would be helpful. Certainly there is general intention to call arbitrary functions on other functions, g(f) === g∘f. It is conceivable that some people would want f-g to mean x -> f(x)-g(x), and similarly f+g, but those seem a big step further, and this PR does not propose them.
Maybe it would be worth thinking up the worst "wat" which this change would allow?
From the point of view of generic programming, !f shouldn't have been added in the first place. The meaning of a generic function should not change depending on the type of the value that is passed to the function. The expression !x normally means "negate the logical value x". But for x::Function it means (!) ∘ x.
Likewise, we should not have two separate meanings for unary -:
-x == -1 * x
-x == (-) ∘ x # If `x` is a `Function`.