Skip to content

Conversation

@mcabbott
Copy link
Contributor

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?

@giordano giordano added needs tests Unit tests are required for this change feature Indicates new feature / enhancement requests labels Sep 28, 2024
@jariji
Copy link
Contributor

jariji commented Sep 28, 2024

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.

This method requires at least Julia 1.12.
"""
-(f::Function) = (-) f
-(f::ComposedFunction{typeof(-)}) = f.inner #allows -(-f) === f
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method seems fit for an independent PR. I think it'd be merged easier than the feature addition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to remove, it's just copied over from the !!f case. Where it allows this slightly odd result:

julia> (!!log)(2)
0.6931471805599453

julia> !!log(2)
ERROR: MethodError: no method matching !(::Float64)

although I doubt that ever matters in practice.

@nsajko nsajko added needs docs Documentation for this change is required needs news A NEWS entry is required for this change labels Sep 29, 2024
@eschnett
Copy link
Contributor

eschnett commented Oct 9, 2024

Do the same for +, out of symmetry?

@adienes
Copy link
Member

adienes commented Oct 9, 2024

personally speaking I don't think I'm a fan of this. the ("wat" potential) : (cuteness of syntax gained) ratio is too high

@mcabbott
Copy link
Contributor Author

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?

@CameronBieganek
Copy link
Contributor

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`. 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Indicates new feature / enhancement requests needs docs Documentation for this change is required needs news A NEWS entry is required for this change needs tests Unit tests are required for this change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants