-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Defining zero() seriously #34003
Comments
The two cases that would change under the proposal above would be preserved if we defined |
Would it make sense to define a zero singleton that just adds to everything the way we want it to, or would that break type stability? |
I hope this does not add too much noise to the discussion, but a similar need (of a "symbolic zero" object |
That's already essentially what |
As it happens I was just thinking about this here: FluxML/Zygote.jl#329 (comment) I think it would make sense to have an analogue to
Maybe for julia> false + [1 0; 0 1]
ERROR: MethodError: no method matching +(::Bool, ::Array{Int64,2}) Also, as @MikeInnes has pointed out, you can't dispatch on |
Another use case I ran into is defining ForwardDiff.partials(x, i...) = zero(x) |
I'd rather not define any arithmetic operation on |
So, this suggests that, when writing code that is supposed to be generic over numbers, we should always use |
Yes, that's essentially what it suggests. And |
But doesn’t using |
I think it seems more sensible if you think of |
Even if it can be justified, it still seems strange to me to write
in order to add 1 to |
I don't disagree; what would you prefer to write? |
We already have a better generic 1: julia> using LinearAlgebra
julia> x = 5f0 + im; x + I
6.0f0 + 1.0f0im
julia> julia> rand(5, 5) + I
5×5 Array{Float64,2}:
1.78862 0.772261 0.661623 0.913183 0.390289
0.366553 1.78299 0.545654 0.433197 0.36714
0.969424 0.646091 1.02947 0.68133 0.284309
0.14118 0.273732 0.127835 1.35712 0.424081
0.180714 0.120144 0.712769 0.548235 1.18592 I think moving |
Note that |
Taking into account the discussion above (and trying to link it back to the original issue): Regarding the zero singleton (for number types and ignoring matrices), my preference would be to simply use This is currently problematic, of course, since these are Some proposals to address this:
In either case, fix the issues raised by @jiahao in the original issue. From a user’s perspective, there isn’t much difference between these. An additional nicety would be to have the compiler convert actual |
If you are going with singleton zero and one, I think it is worth considering supporting arbitrary monoids, as doing so does not require much more effort. I did this in InitialValues.jl and it simplified code doing reduction a lot. The idea is to define something like abstract type Monoid <: Function end
struct Id{F}
op::F
end
struct Add <: Monoid end
const + = Add()
struct Mul <: Monoid end
const * = Mul()
(::F)(::Id{F}, x) where {F <: Monoid} = x
(::F)(x, ::Id{F}) where {F <: Monoid} = x
(::F)(x::Id{F}, ::Id{F}) where {F <: Monoid} = x although Julia does not seem to support the code like the last three lines. But you can use macro to automate it (and defining structs like
One possibility to making code concise with those constants is to use Unicode: const 𝟘 = Zero()
const 𝟙 = One() |
xref: #18367 (comment) (and onward) |
I think it would be very cool if we had true singletons for one and zero, and maybe for +/- infinity, too. I recently came across a need for this with samples and weights. In some use cases, the samples are unweighted, in others not, and I want to write some generic code that can handle both scenarios. So it would be nice to have Also, since we already have singleton for things like |
As I noted elsewhere (#36103 (comment)) adding a |
I would rather see it's main use in generic code, where you'd use an array of I wouldn't use it for accumulation, etc. |
Note that |
Yes, but constructs like But I don't want to focus too much on a specific use case here - I think having zero-mem-cost arrays of ones and zero that can preserve their property under broadcasting (e.g. |
StaticNumbers.jl provides some of this, but it's quite heavyweight (has to be) and also can't define things like |
@stevengj |
Will |
Yeah, it'd be nice to turn this into an API. But, for defining |
I have a feeling these are really separate things:
|
My intention was to share that there is enough usage experience for supporting the usefulness of singleton identity elements. It just happened to be reductions (although it's not a coincidence; that's where you really need to take "zeros" seriously). For other special values like |
Oh, sorry if my statement sounded critical - it wasn't meant to be. I absolutely do agree with you, I think having singleton identity elements would be extremely useful! I was just trying to disentangle them from the mathematical one and zero singletons, since I had a feeling that some doubts expressed here about having
Yes, I was actually thinking about putting together a "OnesAndZeros.jl" or "SpecialNumbers.jl", as a proof of concept. I'm not sure yet how much is possible without committing type piracy regarding to Base, though. But maybe quite a bit. :-) Maybe it can have a |
As for |
Got it. Thanks for clarifying this. My worry is that the same "doubt" could be brought up even for other generic special singleton uses other than the initial values of reductions. If it's in a common API, it could be end up in the state variables of the loops. Of course, this may not happen so frequently because the special singletons are mostly for dispatches (IIUC). Having something like "SpecialNumbers.jl" in the ecosystem would be great for understanding this. |
Ok, I'll go ahead with that, then, hopefully soon. |
I hate to suggest broadening scope, but given this issue and the massive number of trig+pi (sinpi, cospi etc) functions we are starting to get, should this issue instead be taking symbolic numbers seriously and add a |
I think it makes sense to explore the design of singleton/partially-typed numbers here. By the way, the topic in the OP is still important but unfortunately not much discussed. I think it's "orthogonal" to singleton numbers. @jiahao How about re-posting the OP as a new issue? Something similar is required for, e.g., #31635. It'd be nice to have a dedicated place to discuss this. |
I fully agree! |
@perrutquist just pointed me to Zeros.jl so my ideas for a SpecialNumbers.jl are largely realized, already! |
Zeros.jl even includes has a |
It's really cool to see that using Zeros, BenchmarkTools
A = fill(One(), 10^6)
@benchmark all(isone, $A) gives a mean run time of 1 ns, even though Zeros.jl doesn't include a specialized method for that. Kudos to @perrutquist and the Julia compiler team! |
It would be weird if it took time to do a computation on something that takes no memory to store 😁 julia> sizeof(A)
0 |
It would - still, it's cool to see how Julia optimizes the for-loop in |
Even |
It's not quite true, tough. :-) using Zeros, BenchmarkTools
A = fill(One(), 10^6)
@benchmark sum($A) takes 10 μs. But It's strange - |
(This issue is a follow-up to #34000 #28854 #31303)
The docstring for
zero(x)
saysHowever, defining this additive identity precisely seems to be confusing, especially for union types, and in the presence of type promotion rules. The rest of this issue is dedicated to exploring more precisely what the semantics of
zero(T)
is (to help figure out what exactly the semantics should be).zero(::Union)
is undefined when it could be defined. Intuitively, one might expect that the presence of multiple zeros might complicate the definition. However, type promotion seems to allow for a uniquely defined result. If we take the definition above seriously, the statementswould argue that
zero(Union{Float16,Float32}) === Float16(0.0)
(it is currently undefined).Int(0)
is also a valid choice forzero(Union{Float16,Float32})
, sinceHowever, the statement above would also imply that
Int(0)
is a valid choice forzero(Float16)
.We can exclude this value by requiring
zero()
to be endomorphic, i.e.zero(::T) :: T
, and by extensionzero(T) :: T
.Int(0)
appears to be the result ofzero(T)
wheneverT <: Union{Missing,Number,Complex}
, even though it is not always the unique (or even correct) additive identity inT
:Proposal
zero(T)
for non-union types should be defined as the unique additive identity such thata.
zero(T)::T
, i.e.zero()
is an endomorphism, andb. The identity
zero(T) + zero(T) === zero(T)
is satisfied for allT
.zero(Union{T,S}) === zero(T)
whenzero(T) + zero(S) === zero(S)
, taking into account type promotion rules.If no unique endomorphic zero exists, then
zero(T)
should be undefined, except forzero(Bool) === false
as a special case.This definition would preserve the endomorphic property for leaf types, like
zero(Float16) === Float16(0.0)
.This definition would preserve the result for
zero(Bool) === false
, to be consistent with current arithmetic semantics ofBool
.This definition would preserve the semantics of #28854 for
zero(Missing) === missing
.This definition would define
zero(Union{Float16,Float32,Float64})
to beFloat16(0.0)
(currently undefined).This definition would define
zero(Union{Float16,Int16})
to beInt16(0)
(currently undefined).This definition would change
zero(Real)
to befalse
(and similarly in Case 3 above; currentlyInt(0)
); arguably a bug fix.This definition would change
zero(Union{Bool,Int})
to befalse
(currentlyInt(0)
); arguably a bug fix.The text was updated successfully, but these errors were encountered: