Bug/unexpected behavior when extending Base.any with new type

I have extended the any function as shown in the example below:

function Base.any(array::Matrix{Real})
    checker = zeros(Bool, size(array)[1])

    for i in axes(array, 1)
        for j in axes(array, 2)
            if array[i, j] == 1
                checker[i] = true
                break
            end
        end
    end
    return checker
end

The any function should now take in a Matrix that contains elements of the type Number, but when given the following matrix:

A = any([0 0 0 1;
         0 0 0 0;
         0 0 0 1])

We get:

ERROR: TypeError: non-boolean (Int64) used in boolean context
Stacktrace:
 [1] _any
   @ .\reduce.jl:1207 [inlined]
 [2] _any
   @ .\reducedim.jl:1005 [inlined]
 [3] #any#792
   @ .\reducedim.jl:1003 [inlined]
 [4] any(a::Matrix{Int64})
   @ Base .\reducedim.jl:1003
 [5] top-level scope
   @ REPL[149]:1

But when given

A = any([0 0.0 0 1;
         0 0   0 0;
         0 0   0 1])

We get the correct output which is:

3-element Vector{Bool}:
 1
 0
 1

Is this a bug? If I am reporting this in the wrong place, please let me know where to post it.

This is because in your first case you are imputing a Matrix{Int}, which is not a Matrix{Real}, then another any method is being called. You have to change your function to accept Matrix{<:Real}, which are the matrices for which the elements are any subtypes of Real.

With Matrix{Real} you are asking the function to only accept the matrices that are specifically of type Matrix{Real}, which are matrices that accept, on its elements, any type of real number.

Search for the “covariance” of types in Julia, or for example: Vector{Int} <: Vector{Real} is false??? · JuliaNotes.jl, or Why [1, 2, 3] is not a Vector{Number}?

By the way, you can use:

julia> A = [0 0.0 0 1;
            0 0   0 0;
            0 0   0 1];

julia> any.(==(1), eachrow(A))
3-element BitVector:
 1
 0
 1

(ps: calling your function with the second type of matrix doesn’t work here either, because it is a Matrix{Float64} which is not either a subtype of Matrix{Real}. Your method works, as is, if you explicitly set Real[ 0 0 1; 0 1 0 ; 0 0 1 ], such to get Matrix{Real}).

Finally, the error message (which is not great), is saying it is trying to test if a integer number is true or false, that is, using a integer where it expects a boolean. This occurs because the any function supports not providing the first argument (the function to be mapped to every element of the collection), and in that case it tests for true/false the array itself:

julia> any([true false])
true

julia> any(==(1),[1 0])
true

julia> any([1 0])
ERROR: TypeError: non-boolean (Int64) used in boolean context
2 Likes

Even better, use any(x; dims=2):

julia> x = [0 0 0 1;
            0 0 0 0;
            0 0 0 1];

julia> any(x; dims=2)
3Ă—1 Matrix{Bool}:
 1
 0
 1

I never extend basic functionality like this until I’m sure it’s (a) working as I hoped and (b) is operating in a sense consistent with the other methods. Start with your own function names first.

6 Likes

Or any(==(1), x; dims=2) if the matrix is not of integer zeros and ones. By the way, seems slightly inconsistent that this works:

julia> x = [1 0];

julia> any(x; dims=1)
1Ă—2 Matrix{Bool}:
 1  0

while this does not:

julia> any(x)
ERROR: TypeError: non-boolean (Int64) used in boolean context

Why in one case integers are considered “as booleans”, and in the other case don’t? (for the fun I created a PR that fixes this inconsistency, but that seem to be a deliberate choice, so probably that is not going to change).

1 Like

Both your original version or the suggestion to implement Base.any(array::Matrix{<:Real}) are cases of type piracy (see examples). You have modified a function you do not “own” (is not originally defined in the code you’re working on) for a type that you also do not own.

Piracy risks breaking your entire Julia session (and anyone’s who uses your code) in subtle and insidious ways and is strongly discouraged. I recommend using the suggested call to any(==(1), A; dims=2), which is exactly equal to your version but more flexible and not a case of piracy.

6 Likes