1 * ["a"] # gives error
1 * String[] # returns Vector{Union{}}
Why is the second allowed?
1 * ["a"] # gives error
1 * String[] # returns Vector{Union{}}
Why is the second allowed?
We can ask Julia which method implements it
julia> @which 1 * String[]
*(A::Number, B::AbstractArray)
@ Base arraymath.jl:21
This makes sense as the idea is c \cdot v for c \in \mathbb k and v a vector would then become a component-wise multiply, useful when you implement your own types.
Hopefully this explains why it is allowed, but another question would be should such behaviour be considered wrong.
I understand that a scalar times a vector is essentially a component-wise multiplication. (I think it calls broadcast?). But my question is, why is the operation *
between an Int64
and a Vector{String}
allowed when *(::Int64, ::String)
is not defined?
You can not do, for example, 1 * "a"
because *(::Int64, ::String)
is not defined. And this is the error you get when you try 1 * ["a"]
as well. That makes sense. So then why allow 1 * String[]
?
*
between Int64
and Vector{String}
is defined. It will try to broadcast the multiplication across the vector. *(Int64, String)
does not exists to trying to call it will result in a MethodError
. But here is the catch: Since your vector is empty, no such call occurs, so no error is thrown!
You can also see this from the type of the resulting Vector{Union{}}
. Union{}
means there are no possible types that could fit into this vector which is a consequence of *(Int64, String)
not being defined.
Perhaps, we could also see this case as an instance of “everything is true of the elements of the empty set”.
Ok, the type of String[]
is getting ignored there. @assert eltype(String[]) == String
.
I don’t know if I like empty arrays. I tend to use them as default values. I’ll need to rethink them and make sure to treat them as special cases. It turns out that all elements of empty array are true
, even when the array cannot hold Bool
s:
@assert all(String[])
But all elements of the same, non-Boolean empty arrays are also false:
@assert !any(String[])
I don’t understand why the eltype gets ignored.
Perhaps this topic can help:
Thanks, @Sukera. I had no doubt that people had put thought into the choice of behavior. But things are not any clearer to me. (And maybe that’s okay.) I suppose all programming languages have to deal with empty sets/arrays. There are two things that confuse me.
Why all
and any
and the like work on non-Boolean arrays at all. Probably for the same reason that 'a' + 1 == 'b'
and 1 == true
.
Why the need to return an answer when there isn’t one? Maybe any([])
and all([])
should be neither true
nor false
. I’d rather deal with an error there and implement an if !isempty
to deal with the error than have some unexpected value floating around my program. And it’s not even that all
and any
necessarily return Bool
. They return missing
when the iterator contains missing values.
They only work in the empty case, because the empty case doesn’t care about the type; there is no logical predicate you can write that will make the any
call return true
, because it won’t ever be applied. Once you have a non-empty array, you do get an error:
julia> any(["foo"])
ERROR: TypeError: non-boolean (String) used in boolean context
Stacktrace:
[1] _any
@ Base ./reduce.jl:1228 [inlined]
[2] _any
@ Base ./reducedim.jl:1007 [inlined]
[3] any(a::Vector{String})
@ Base ./reducedim.jl:1005
[4] top-level scope
@ REPL[1]:1
But there is one - any(f, vec)
is asking the question “of all the elements in vec
, is there one that fulfills the predicate f
?” and for an empty array the answer is false
, because there is no example from vec
you could provide to make that true
. You can think of any
/all
as a fold
, if you will, starting out with initial values of false
and true
respectively. If there are no elements to fold over, it’s just the initial value being returned.
That’s just a property of missing
though, and unrelated to any
/all
. Your predicate is free to handle missing
differently.
The simplest mental model here is that any
is true if there’s at least one true and all
is false if there’s at least one false. Array element types don’t matter; it’s the values that do. In fact:
julia> all([false, "no errors!"])
false
julia> any([true, "no errors!"])
true
any(f, vec) is asking the question “of all the elements in vec, is there one that fulfills the predicate f?”
I like the framing as a mnemonic:
any(f, vec): There is an element in vec that fulfills the predicate f.
all(f, vec): There is no element in vec that fails to fulfill the predicate f.
But again, this leads to the contradiction (all(f, []) && !any(f, [])) == true
, as mentioned in the post linked earlier. And now that I think about it, all(f, []) == all(!f, []) == true
.
So we’re just kicking the can down the road-- does the element of an empty set, the one that does not exist, fulfill or fail to fulfill a predicate?
I wrote this just to think through the issue a bit, and defaults are really sneaky when dealing with empty sets. I wanted to leave it here as a cautionary tale.
function allisodd(set::Vector)
for element in set
if isodd(element) == false # element fails to fulfill predicate
return false
end
end
return true
end
function noneisodd(set::Vector)
for element in set
if isodd(element) == true # element fulfills predicate
return false
end
end
return true
end
With this, we get allisodd([]) == noneisodd([]) == true
I’ll live with the mnemonic for now. Thanks for engaging.