Bools in arithmetic operations

Bools seem to get converted/promoted to numerical zero/one:

julia> false + 42
42

julia> true + 42
43

julia> false + 3.14
3.14

julia> true + 3.14
4.140000000000001

julia> false + complex(1, 2)
1 + 2im

julia> true + complex(1, 2)
2 + 2im

I don’t like it. For example,

julia> gety(m, index_offset=0) = m[:, index_offset+2]
gety (generic function with 2 methods)

julia> m=[1 3 5 8;2 11 9 6]
2×4 Array{Int64,2}:
 1   3  5  8
 2  11  9  6

was accidently called as

julia> gety(m, true)
2-element Array{Int64,1}:
 5
 9

The bug was difficult to hunt down.

Is it possible to optionally suppress such automatic conversions in arithmetic operators?

For the general question of “why is Bool an Integer, see:

https://github.com/JuliaLang/julia/issues/19168

For your particular case, if you don’t want Bool to be accepted as input in your function, just make it throw an error in such a case:

julia> gety(m, ::Bool) = throw(ArgumentError("Bool not allowed"))
gety (generic function with 1 method)

julia> gety(m, true)
ERROR: ArgumentError: Bool not allowed
Stacktrace:
 [1] gety(::Array{Int64,1}, ::Bool) at .\REPL[1]:1
 [2] top-level scope at REPL[2]:1234:1
6 Likes

Bools are neither converted nor promoted to numerical zero/one, they are numerical zero/one.

julia> supertype(Bool)
Integer
4 Likes

Thanks for the link. It shows that this is a complicated issue, which I had already suspected.

In reality I have of course many candidate functions which should not work with Bool arguments. For now I restrict them like

julia> getty(m, index_offset::Int=0) = m[:, index_offset+2]
getty (generic function with 2 methods)

which is more restrictive than needed but easier to exclude Bool arguments.

Dispatching on only Int is too restrictive. Generic code is normally preferable, so it’s better to define special behavior only for the exceptional cases you want to rule out (Bool input, in this case).

But if you don’t want to write the extra methods to manage those exceptions, at least I recommend you to allow any other Integer that is not Bool. Bool happens to be the only subtype of Integer which is not either Signed or Unsigned, so you could write:

getty(m, index_offset::Union{Signed, Unsigned}=0) = m[:, index_offset+2]

While restricting the input is an option to catch the error at that point, it seems that the bug was probably before the call to your function, somewhere where a Bool was generated where an integer was expected to be generated. Perhaps the proper place to add an error message or deal with the Bool was at that point, if for some reason that turns out to be possible and the program is expected to stop if that occurs.

(otherwise, although you can prevent that error from propagating in your own functions, it will propagate in every other functions of other packages or Base that accept Bools as integers, which are probably most of them)

6 Likes

Index inputs are converted to Int by the to_indices function. One generic way of doing this, in that case is

gety(m, index_offset::Int=0) = m[:, index_offset+2]
gety(m, index_offset) = gety(m, to_indices(m, (index_offset,))...)

This should accept all the same types of indices that other Julia arrays do, staying up-to-date with whatever to_indices does. Specifically, Bools will be disallowed, while other integers are accepted.

(Please be aware, I’ve never had reason to use to_indices before, so double check that it works as expected.)

1 Like

Thanks for the replies, there are several soulutions to my problem. I add now

const ExceptBool = Union{Signed, Unsigned}

to my relevant modules (or perhaps even to startup.jl), and use it in functions like the above mentioned example

gety(m, index_offset::ExceptBool=0) = m[:, index_offset+2]

to enforce that using Bools for arguments in such functions must be explicit:

julia> gety(m, true)
ERROR: MethodError: no method matching gety(::Matrix{Int64}, ::Bool)
Closest candidates are:
  gety(::Any) at REPL[10]:1
  gety(::Any, ::Union{Signed, Unsigned}) at REPL[10]:1
Stacktrace:
 [1] top-level scope

julia> gety(m, Int(true))
2-element Vector{Int64}:
 5
 9

Alternatively, you can just do

@assert !(index_offset isa Bool)

inside functions. It will be costless if your types are inferred.

9 Likes