Curried Broadcasting Comparison Operators

This is not what I expected:

julia> >(2) === .>(2)
true

julia> .>(1:4)
4-element Vector{Base.Fix2{typeof(>), Int64}}:
 (::Base.Fix2{typeof(>), Int64}) (generic function with 1 method)
 (::Base.Fix2{typeof(>), Int64}) (generic function with 1 method)
 (::Base.Fix2{typeof(>), Int64}) (generic function with 1 method)
 (::Base.Fix2{typeof(>), Int64}) (generic function with 1 method)

Intuitively, I thought .>(y) would be a broadcast(>, _, y) partial applicator (in lambda terms, x->broadcast(>, x, y)), but instead it’s a broadcast(Base.Fix2, >, y)—making it so that it’s not a function, but an array of functions, and thus is not a callable object.

This is inconsistent with, for example, .!, which is a callable object and can be directly called on a collection:

julia> .!((true, false, true))
(false, true, false)

For comparison, .>(y) can’t be called on anything:

julia> (>(2))(1:4)
ERROR: MethodError: no method matching isless(::Int64, ::UnitRange{Int64})

julia> (.>(2))(1:4)
ERROR: MethodError: no method matching isless(::Int64, ::UnitRange{Int64})

julia> (.>(1:4))(2)
ERROR: MethodError: objects of type Vector{Base.Fix2{typeof(>), Int64}} are not callable

julia> (.>(1:4)).(2)
ERROR: MethodError: objects of type Vector{Base.Fix2{typeof(>), Int64}} are not callable

julia> (.>(1:4))(4:-1:1)
ERROR: MethodError: objects of type Vector{Base.Fix2{typeof(>), Int64}} are not callable

julia> (.>(1:4)).(4:-1:1)
ERROR: MethodError: objects of type Vector{Base.Fix2{typeof(>), Int64}} are not callable

I can only get it to do something under three scenarios:

julia> (.>(2))(2)
false

julia> (.>(2)).(1:4)
4-element BitVector:
 0
 0
 1
 1

julia> ((x,y)->x(y)).(.>(1:4), 4:-1:1)
4-element BitVector:
 1
 1
 0
 0

However, the first two scenarios are already covered by (non-broadcasted) >(y):

julia> (>(2))(2)
false

julia> (>(2)).(1:4)
4-element BitVector:
 0
 0
 1
 1

So really, .>(y) (and all the other curried broadcasting binary operators) only do something useful in the case where a function which calls another function is being broadcasted.

Maybe I’m just unimaginative, but can someone help me out with finding a use case for this, and why this behavior is preferred over a partial function applicator on broadcast, e.g. Fix{(1,3),3}(broadcast, >, 1:4) (supposing that specializations on broadcast would be written for Fix{typeof(broadcast)} objects)? I can imagine use cases for (.>(2))(1:4) or for (.>(1:4))(2), but I’m having trouble imagining use cases for its current behavior.

It’s just how broadcasting works in Julia: f.(1:4) == [f(1), f(2), f(3), f(4)].
Substitute f with >, and you get .>(1:4) == [>(1), >(2), >(3), >(4)], that is an array of functions.
And of course this is consistent with !: .!((true, false, true)) == (!(true), !(false), !(true)) == (false, true, false).

1 Like

Relevant discussion: Broadcasting tuple functions · Issue #22129 · JuliaLang/julia · GitHub