Closed LeaVerou closed 5 months ago
So now we’ve announced that the change is coming. How should we go about with this? How much effort should we put in making the migration gentler?
NaN
is used, and silently convert it to null
?NaN
and null
when found in coords, and print out a one-time warning for NaN
?So, I'm curious, are NaN
and none
(null
) to behave identically and only serialize differently? Are NaN
values meant to be disallowed from the future syntax of Color CSS? Or are both going to be allowed?
Mathematically, they will not behave the same. I guess it will be easier to miss when null
handling isn't preserved as null
participates as 0
in math, but NaN
is infectious and always yields NaN
in math. But I guess also less likely to break in big ways, probably more subtle.
The more I think about it the more questions I have. If both are preserved and you are interpolating between both NaN and null, which is returned, NaN or null?
This seems like an unfortunate complication in CSS colors. It doesn't seem to provide anything extra, only confusion. It's a shame none
can't just be an alias for NaN in colors, or that NaN just wasn't used from the beginning.
Is there value in preserving both NaN and null in colors? Especially if they are meant to behave the same? I guess I would opt for a solution that normalizes these two values. I'm not sure how having both null and NaN helps users.
So, I'm curious, are
NaN
andnone
(null
) to behave identically and only serialize differently?
Nope, they mean different things. I was only suggesting they behave identically in v0.6.0 as a way to ease the transition.
The more I think about it the more questions I have.
These are excellent questions for CSS Color! Pinging the other editors, @svgeesus @tabatkins
NaN
is not color specific, it's a css-values thing: https://drafts.csswg.org/css-values/#calc-error-constants
That said, since it exists in the language, it could be specified in a color. I would expect it behaves the same as any other number, meaning…
If both are preserved and you are interpolating between both NaN and null, which is returned, NaN or null?
NaN
, since any number interpolated with none
returns that number.
This seems like an unfortunate complication in CSS colors. It doesn't seem to provide anything extra, only confusion. It's a shame
none
can't just be an alias for NaN in colors, or that NaN just wasn't used from the beginning.
I believe @tabatkins has strong opinions on why that would have been a mistake, and I hope he is willing to explain or point to his comments on the matter (FWIW Chris and I had your view and he convinced us 😬 )
Is there value in preserving both NaN and null in colors? Especially if they are meant to behave the same? I guess I would opt for a solution that normalizes these two values. I'm not sure how having both null and NaN helps users.
They are not meant to behave the same, nor do they represent the same thing, see above.
They are not meant to behave the same, nor do they represent the same thing, see above.
The representation difference I get. One is number thing and maybe stuff like calc()
can return NaN
, and in colors, none
represent undefined values, but how do they differ in behavior?
So, functionality-wise, none
is not infectious like NaN
or is it? I get specifically in interpolation that none
takes on the value of the other color. But if not in interpolation what happens? If it isn't infectious, I imagine it is very easy for it lose its meaning accidentally if you aren't careful.
Does NaN
no longer obey the same interpolation rules as none
? Does it just infect the other color with itself during interpolation? I guess this would mean it isn't checked for and just allowed to pass through as a normal number to wreck havoc?
Are NaNs no longer resolved to a real value at any time? The color spec doesn't speak to any of this currently. As far as Im aware, it still speaks to NaNs representing undefined coordinates.
I had remembered the discussion about using NaN vs none and found it here: https://github.com/w3c/csswg-drafts/issues/6107. I think it didn't bother me at the time because, functionality-wise, the end result was going to be the same, or at least seemed to be. It seemed more of a syntax issue difference making clear the difference of what NaN
meant vs none
, but I guess it didn't clear up behavior.
At the time, there was no NaN
in CSS. I think now that NaN
is being introduced with none
, now things become murky, and I don't think the spec really clears up how one behaves vs the other in colors. Or maybe I was always wrong and none
was not meant to behave like NaNs
.
Okay, I did find this in the Color 5 spec:
However, if calculations are done on missing values, none is treated as 0.
I guess this explains the treatment of undefined as a null. So, I guess that answers my question. Undefined is no longer infectious (which is a shame because it made sense), but I get its context when looking at CSS. And NaN just behaves as NaN. So this change will be a surprising change in Color.js.
So, functionality-wise,
none
is not infectious likeNaN
or is it? I get specifically in interpolation thatnone
takes on the value of the other color. But if not in interpolation what happens?
It's exactly the opposite actually: none
+ anything that is not none
= not none
When doing math (e.g. calc(h + 10)
) or color space conversions, none
gets coerced to 0 IIRC (@svgeesus confirm?)
If it isn't infectious, I imagine it is very easy for it lose its meaning accidentally if you aren't careful.
Precisely! That's why it's important not to do conversions we don't need (such as converting colors that are in-gamut in a GMA that doesn't modify in-gamut colors).
Does
NaN
no longer obey the same interpolation rules asnone
? Does it just infect the other color with itself during interpolation? I guess this would mean it isn't checked for and just allowed to pass through as a normal number to wreck havoc?
I would imagine that interpolating NaN
with none
would just make the whole component NaN
. @svgeesus we need to make sure the spec is clear here.
Are NaNs no longer resolved to a real value at any time? The color spec doesn't speak to any of this currently. As far as Im aware, it still speaks to NaNs representing undefined coordinates.
NaN
is a first-class value in CSS, it resolves to itself.
I guess this explains the treatment of undefined as a null. So, I guess that answers my question. Undefined is no longer infectious (which is a shame because it made sense), but I get its context when looking at CSS. And NaN just behaves as NaN. So this change will be a surprising change in Color.js.
I don't understand what you're saying here, could you elaborate a bit more? Why is it a shame that undefined
is no longer infectious? How are we treating undefined
as null
? (we shouldn't!) And why would it be a surprising change?
I don't understand what you're saying here, could you elaborate a bit more? Why is it a shame that undefined is no longer infectious? How are we treating undefined as null? (we shouldn't!) And why would it be a surprising change?
Undefined is no longer NaN
, we are using null
, quite literally, and the behavior is exactly described as null. Their behavior is very different. We are replacing one concept with the other, which is fine. It is a sizeable breaking change, but I get where it is coming from.
Before, undefined operated as NaN
, at least how it was implemented. Originally that was intentional, obviously not anymore. It had no value; therefore, you could not do calculations with it, not until you explicitly defined it. This made sense, at least to me. How can I add to something that doesn't exist?
> a = NaN
NaN
> a + 3
NaN
> a = 0
0
> a + 3
3
Now it is null, and we will quite literally be using null. It is uniquely different, and has its own identity up until you do anything with it, then it is zero (at least in most languages). This describes the behavior in the spec and it quite literally mimics how null
behaves.
> a = null
null
> a + 3
3
It's a change in concept and any library that implemented the NaN
behavior will have to consider, do we require 100% parity with CSS or do we support parsing CSS color syntax only, which is fine. Color.js wants to represent CSS, not only in syntax, but behavior.
As a side note, I've personally found the very specific behavior of undefined == 0 (whether mimicking NaN or null) to sometimes be lacking in the user experience, from a general purpose color library standpoint. Consider below.
>>> Color('black').convert('acescc')
color(--acescc -0.35845 -0.35845 -0.35845 / 1)
>>> Color('black').convert('acescct')
color(--acescct 0.07291 0.07291 0.07291 / 1)
Neither of these two spaces define black with zeros , not that undefined has to resolve to black, per se, but if we apply them, we get out of gamut results for acescct. Not very helpful.
>>> Color('acescct', [0, 0, 0]).convert('srgb')
color(srgb -0.07781 -0.07781 -0.07781 / 1)
These cases are rare, but this inflexibility can break in certain aspects. I initially had this issue with spaces like HCT and JzCzhz, has zero chroma and zero hue no longer mean white, gray, etc. (at least not pure white and gray). At least with these spaces, I think I'm starting to come to the realization that they are accounting for adapting luminance and background luminance, and when you do this, the achromatic colors appear different than pure white, pure gray, etc. If you "discount the luminance", you get a more traditional achromatic response of pure achromatics. So maybe the concept of zero chroma and zero hue must be pure achromatic was flawed initially, at least when looking at JzCzhz, CAM16, ZCAM, etc.
I may be incorrectly lumping JzCzhz in with CAM16 and ZCAM, it has a similar achromatic response, but maybe its response is simply based off of less accurate matrices 🤷🏻.
The arguments against lumping NaN with none in CSS were in https://github.com/w3c/csswg-drafts/issues/6107, especially this comment. In short: while NaN can serve as a reasonable sentinel value in many situations (because it's not part of the normal value space), you don't actually want author-produced NaNs (which come from math errors) to trigger this behavior. CSS can easily avoid the sentinel value issue and just use a different value (a keyword, in this case), thus treating any NaNs that are accidentally fed into the functions as an error case.
However, @LeaVerou, note that the color functions never see a NaN. A NaN gets censored into infinity
when it exits the the math function, and so the color function only ever sees infinity (which it then arbitrarily treats as 0deg). So there's no CSS-compat reason to support reflecting NaN in colors.
However, @LeaVerou, note that the color functions never see a NaN. A NaN gets censored into infinity when it exits the the math function, and so the color function only ever sees infinity (which it then arbitrarily treats as 0deg). So there's no CSS-compat reason to support reflecting NaN in colors.
Ah, that is correct, we can't directly use NaN or Infinity in colors as things are currently, not as far as CSS syntax is concerned. They would have to be wrapped in calc()
, something they can't escape.
So really, assuming Color.js really prefers nulls in general, it could just convert NaNs to null and call it a day. If a change must be made, I imagine this would be the least breaking. So what this really boils down to is whether color.js prefers NaN behavior or null behavior.
@tabatkins I thought users can also directly specify NaN
?
@facelessuser Even if NaN
s don't naturally appear in CSS colors, they can absolutely appear in JS when math is involved, and they should be distinct than a value that means "take the other component when interpolating".
@facelessuser Even if NaNs don't naturally appear in CSS colors, they can absolutely appear in JS when math is involved.
Yep, that is certainly true.
Putting my opinions aside, I think I understand the situation and how the behavior will change now, and I now understand the reasoning behind it.
Because this is still a < 1.0 version, and I'd hate to see the overhead required to validate every value on every set, I'm kind of leaning more towards just making the change, with a bold note in the changelog of the breaking change.
I thought users can also directly specify NaN?
Nope, they can say calc(NaN)
, but that's still identical to calc(infinity)
.
This issue is a blocker for v0.6.0 so I’m keen to resolve it ASAP.
Thinking back to…
So now we’ve announced that the change is coming. How should we go about with this? How much effort should we put in making the migration gentler?
- Should we just make the change and just announce it in the changelog, since we gave people a heads up? This involves the least effort on our side.
- Should we print out a one-time warning about the change if
NaN
is used, and silently convert it tonull
?- Should we handle (and preserve) both
NaN
andnull
when found in coords, and print out a one-time warning forNaN
?- Something else?
It seems like there was no resolution?
If so, I think this what I’ll do:
none
as null
NaN
as NaN
null
as none
NaN
as calc(NaN)
Should NaN be converted to 0 when converting between colors or is it left as NaN?
Should NaN be converted to 0 when converting between colors or is it left as NaN?
I think it's fine to leave it as NaN, but feel free to open another issue to discuss that.
1) I just came back to fiddling with Color.js and I noticed that .coords
returns null
where it had previously returned NaN
, equivalent to CSS none
. Based on this discussion, I see that NaN
as a coordinate value now has the standard meaning of erroneous numeric value. In terms of coordinates, are those the only two changes?
2) I can't conceive of a situation where I'd intentionally put NaN
anywhere in a CSS rule, so it must apply to function return values, like calc()
. That would quasi-align with the use of calc(NaN)
earlier in this discussion. Is that correct?
This was a resolution in a past breakout with @svgeesus but I’m opening this so we can track it.
Color.js currently uses
NaN
values to represent CSSnone
(e.g. for achromatic colors). However, CSS also now has aNaN
value, which is currently impossible to represent in Color.js in a distinct way.Therefore, In the next non-minor version, we will start using
null
to representnone
.Authors can actually handle both, by detecting which value is used via:
They can then use
NONE_COORD
instead of a hardcodedNaN
so that nothing breaks in their code once they upgrade.I therefore plan to announce this change in the release notes for v.0.5.0 and recommend they handle it that way to prepare.