Skip to content
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

Stop using / as division #2565

Open
nex3 opened this Issue Sep 21, 2018 · 20 comments

Comments

Projects
None yet
7 participants
@nex3
Copy link
Contributor

nex3 commented Sep 21, 2018

The current plan:

  1. Create an automated migration tool that can migrate users from /-as-division to a divide() function.
  2. Deprecate /-as-division. Any time a / would be interpreted unambiguously as division, or an ambiguous / would be made unambiguous (for example by being stored in a variable), we produce a deprecation warning .
  3. Add a top-level divide() function (math.div() in the new module system). This will work exactly the same way division works today, with the obvious exception that it will never be treated as a slash separator.
  4. Add a new slash-separated list type to represent slash as a separator. For the time being, this will not have a literal syntax.
  5. Add a slash-list() function (list.slash() in the new module system) that returns a slash-separated list. Providing the ability to work with slash-separated lists even before they have a literal syntax will help stylesheets be forwards-compatible with the new wold.
  6. Wait until at least three months after the module system is released.
  7. Release Dart Sass 2.0.0, which parses / as a list separator that binds more loosely than space but more tightly than comma.
  8. Add support for first class calc(), which will allow /-as-division in the form of calc(5px / 2).
  9. Deprecate the slash-list()/list.slash() function in favor of using the literal syntax.
  10. Whenever Dart Sass 3.0.0 releases, remove slash-list() and list.slash().

The original issue:

When Sass was first designed, the most prominent use of / as a separator in CSS was the short-form font property, which used it to separate values for font-size and line-height. Because this was a relatively narrow edge case, we were able to use / as the division operator as well, with some heuristics to decide between them. When / was used as a separator, the value it produced was actually the divided number, but it was rendered with the slash instead.

However, the slash separator has been seeing more use in recent CSS specs. The grid-row property uses it to separate values for grid-row-start and grid-row-end, and Colors Level 4 uses it in many color functions (including the new recommended syntax for rgb() and hsl()) to separate the alpha value from the rest of the parameters.

The increased prominence of this syntax means that the sharp edges of the heuristics we use to distinguish between /-as-separator and /-as-division will start cutting more users. Once Sass supports the new color function syntax (#2564), users are likely to try passing the alpha by variable, which will currently fail. We need to decide whether to do something about this, and if so, what.

How do we represent division?

Today, division is represented by the operator /, which as discussed above is ambiguous. We could fairly easily add a new syntax for this, which would allow us to migrate users away from /, eventually leaving it as unambiguously a separator.

I see a few options for this:

  • Keep the behavior as-is. This has the substantial benefit of requiring no migration, and it will be sufficient to support compatibility with plain CSS. However, it will make it much more painful for users to use dynamic alpha values for their colors as the new color function syntax gains traction.

  • Replace the / operator with another operator. There's no option that's really consistent with other programming languages, but ~ is a possibility that's currently unused and looks a little like ÷.

  • Replace the / operator with a function, such as div(). This is more verbose, but it's very clear to the reader and it avoids any risk of further collision with new CSS syntax.

What is a slash separator?

If we do decide to free up / to unambiguously represent a separator, we need to figure out how that separator works in terms of SassScript. This is particularly tricky because its scope varies from property to property. In font, it's tightly bound--the slash in italic small-caps bold 16px/2 cursive separates 16px from 2, but it doesn't separate italic small-caps bold 16px from 2 cursive. However, in grid-row and the color functions, it's loosely bound--the slash in rgb(1 2 3 / 0.5) separates 1 2 3 from 0.5, and is not bound to 3 in particular.

I think it makes sense in general to consider / another kind of list separator, so that it works smoothly with existing APIs. But because of the scoping differences it's not clear whether it should bind more tightly than space-separated lists or not. That is, should 1 2 / 3 4 be equivalent to (1 2) / (3 4) or 1 (2 / 3) 4?

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Sep 21, 2018

@tabatkins Any insight you can provide would be welcome. In particular:

  • How likely is the / separator to continue to be used in new rules/functions?

  • If / is used more, is it likely to bind tightly or loosely?

  • How likely is ~ to appear as syntax in a property value?

@jelbourn

This comment has been minimized.

Copy link

jelbourn commented Sep 21, 2018

Would it make sense to continue interpreting / as division in places where it would otherwise be invalid CSS, otherwise treating it as the native separator? That would necessitate introducing a division function, but it would only need to be used when there would be ambiguity. The compiler could error on any ambiguity.

Admittedly, this could get confusing. It might be valuable to analyze existing libraries to see how prevalent ambiguities would actually be.

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Sep 21, 2018

Sass doesn't have any contextual information about the property which it's evaluating code for. This is both because we have a general policy of encoding as little information about CSS itself as possible, and because there's no way to know what property a given variable or argument might be used with. Without that, I'm not sure there's a way to know which uses of / would be valid CSS and which wouldn't.

@rickcnagy

This comment has been minimized.

Copy link

rickcnagy commented Sep 21, 2018

Is there any reason that // isn’t an option? It would be similar to Python 3.

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Sep 21, 2018

// is already the syntax for a single-line/silent comment.

@tabatkins

This comment has been minimized.

Copy link

tabatkins commented Sep 21, 2018

How likely is the / separator to continue to be used in new rules/functions?

We will continue using it rarely, as we do today, as a secondary separator.

If / is used more, is it likely to bind tightly or loosely?

The general rule, which we almost always stick to, is that CSS separates repetitions of values by ,, then within each repetition (or within the whole value, for non-repeating properties), primarily uses (space) to separate tokens, only using / as a separator when required due to ambiguity. We don't have a strict notion of binding strength for the /; the only firm rule is that , binds more loosely than space or /.

(More precisely, sometimes / binds tightly to the next token, like in font or rgb(), while in others it binds loosely and separates groups of space-separated tokens, like in border-radius or border-image.)

How likely is ~ to appear as syntax in a property value?

Beyond it's current use in selectors, I don't expect it to show up property values; we tend to be conservative about new characters to reduce grawlix-ness. That said, it's still possible that we add a selector() function or similar at some point in the future, allowing ~ to show up at the property level. (Selectors will not show up "unshielded" by a function tho, I'm sure of that; their syntax is too open-ended to reasonably do that.)

That said, the fact that it is already used in Selectors does reduce the friction for using it some time in the future; other proposals for using new ASCII characters sometimes founder on "that's easy to type on an English keyboard, but not on my [other-language] keyboard", but ~ is already a sunk cost in that regard.

We did vacate the naked-parenthesis space for y'all, and I'm willing to defend that line, so if you want to reserve "within ()" as the space for safe free-form math, I think you can safely do so. Or, of course, just use "calc()". ^_^

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Sep 21, 2018

The general rule, which we almost always stick to, is that CSS separates repetitions of values by ,, then within each repetition (or within the whole value, for non-repeating properties), primarily uses (space) to separate tokens, only using / as a separator when required due to ambiguity.

Out of curiosity, why did you end up with rgb(r g b / a) rather than rgb(r g b a)? It seems like the latter shouldn't be ambiguous.

(More precisely, sometimes / binds tightly to the next token, like in font or rgb(), while in others it binds loosely and separates groups of space-separated tokens, like in border-radius or border-image.)

I actually hadn't realized that border-radius and border-image had / as part of their value grammars. In that case, binding looser than space definitely hits the most properties today, and there's no reason to think that won't hold true in the future. It makes the parse tree for font pretty weird, but it will still render fine.

That said, it's still possible that we add a selector() function or similar at some point in the future, allowing ~ to show up at the property level. (Selectors will not show up "unshielded" by a function tho, I'm sure of that; their syntax is too open-ended to reasonably do that.)

Anything shielded in a function name is totally fine for us syntax-wise. We have a strong precedent with calc() for having special parsing for certain functions.

That said, the fact that it is already used in Selectors does reduce the friction for using it some time in the future; other proposals for using new ASCII characters sometimes founder on "that's easy to type on an English keyboard, but not on my [other-language] keyboard", but ~ is already a sunk cost in that regard.

That also makes it appealing for us. On the whole, it sounds like ~ is a likely to be our best option if we do decide to continue to use a single character.

We did vacate the naked-parenthesis space for y'all, and I'm willing to defend that line, so if you want to reserve "within ()" as the space for safe free-form math, I think you can safely do so. Or, of course, just use "calc()". ^_^

Leaving parentheses alone has been extremely helpful 🙇‍♀️, and in fact it is one of the heuristics we use today to decide what to do with /. But as discussed above, those heuristics are likely to become more annoying the more prevalent slash-separation becomes.

@tabatkins

This comment has been minimized.

Copy link

tabatkins commented Sep 22, 2018

Out of curiosity, why did you end up with rgb(r g b / a) rather than rgb(r g b a)? It seems like the latter shouldn't be ambiguous.

It's not technically ambiguous in rgb() and hsl(), but the / does serve as a nicely visible indicator that "hey, this color has an explicit alpha": in rgb(0 0 0 0) vs rgb(0 0 0) vs rgb(0 0 0 / 0), the first two aren't super-apparent on first glance, but the last two are really obvious.

But we were also trying to solve the problem for color(), which can take any number of arguments after the colorspace name, and we wanted to enable some of those to be optional. There's no easy way to add an easily-readable, easily-writeable, unambiguous alpha value there, without relying on some form of separator, and we already wanted to use the , for its normal "separating repetitions" functionality, so you could supply a fallback color. / ends up serving its "disambiguate a value from similar surrounding values" function really well there, so we applied it globally to the other colors.

Finally, as a minor but significant additional point, gray() only takes a single color-relevant argument, so with alpha it has two values. Between rgb/hsl/lab/lch all taking 3+alpha, color taking n+alpha, and gray taking 1+alpha, having an explicit visual indicator of when the alpha was being set makes things more readable in general, without having to remember how many arguments any particular function takes before the alpha.

Anything shielded in a function name is totally fine for us syntax-wise. We have a strong precedent with calc() for having special parsing for certain functions.

Cool. We'll never have a selector more complicated than an ID unshielded. (I know that still offers some ambiguity for you, as both IDs and hex colors use the same token, but eh. Most of the time it's still unambiguous due to character sets. ^_^)

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Mar 5, 2019

I discussed this with a crack team of Sass users at Google, and we came up with the following plan:

  1. Deprecate /-as-division. Any time a / would be interpreted unambiguously as division, or an ambiguous / would be made unambiguous (for example by being stored in a variable), we produce a deprecation warning .
  2. Add a top-level divide() function (math.div() in the new module system). This will work exactly the same way division works today, with the obvious exception that it will never be treated as a slash separator.
  3. Add a new slash-separated list type to represent slash as a separator. For the time being, this will not have a literal syntax.
  4. Add a slash-list() function (list.slash() in the new module system) that returns a slash-separated list. Providing the ability to work with slash-separated lists even before they have a literal syntax will help stylesheets be forwards-compatible with the new wold.
  5. After a suitable deprecation period, start parsing / as a list separator that binds more loosely than space but more tightly than comma.
  6. Deprecate the slash-list()/list.slash() function in favor of using the literal syntax.
  7. In the next major revision, remove slash-list() and list.slash().

This is not set in stone. It's a big change, and I want to make sure people have a chance to provide feedback before I start working on a more formal spec. I'll leave this open for at least a few weeks so folks have a chance to chime in.

One major open question (at least for Dart Sass) is, how long do we leave the deprecation before we drop support for / as division, and do we do a major version bump at that point? Because this is a CSS compatibility issue, Dart Sass's compatibility policy gives us license to remove the old behavior as early as three months after the deprecation warning is released, without a major version bump. However, this affects a much broader range of stylesheets than most, so I think that's too short. Would six months but no major version bump work?

@nex3 nex3 added planned and removed under consideration labels Mar 5, 2019

@fedfigca

This comment has been minimized.

Copy link

fedfigca commented Mar 5, 2019

My ¢2 is that / is commonly used as division almost everywhere else, using a different syntax will be yet another challenge for new users, and for old code alike, so trying to keep it should be the priority.

Using some sort of encapsulation for the arithmetic might be one way to proceed in places where the slash could mean a separator, or any other way, but if we have to teach new users (who probably already program other languages) a different syntax for math, it might become one of those issues that make people look for alternatives.

@metavurt

This comment has been minimized.

Copy link

metavurt commented Mar 6, 2019

is that / is commonly used as division almost everywhere else

Guess what. CSS is not "everywhere else", and, just like any programming language, CSS has a designed syntax agreed upon not just by open source organizations but also the platforms in which we see CSS in action.

@fedfigca All CSS developers need to [came across too strongly here] get up to speed and come to recognize the paradigm shift that is CSS Grid. It was built specifically to fix a two-decades-old problem in the CSS spec of there not being any one tool specifically for layout. We have used hacks and workarounds for over 20 years because we never had CSS grid from the outset.

Now we do, and part of its designated and designed spec is using the forward-slash as a means of defining columns and rows, and spans of columns and rows for any grid child.

Any NEW user of CSS should be learning that for layout, we now use CSS Grid. Period. Full stop.

Saying, "so trying to keep it [forward slash as division only in Sass] should be the priority" is really ignoring the larger picture of the progression of the language Sass is built to parse.

@metavurt

This comment has been minimized.

Copy link

metavurt commented Mar 6, 2019

@nex3 I do not envy you, and wish you the best of luck in moving forward with the planned roadmap.

@fedfigca

This comment has been minimized.

Copy link

fedfigca commented Mar 6, 2019

is that / is commonly used as division almost everywhere else

Guess what. CSS is not "everywhere else", and, just like any programming language, CSS has a designed syntax agreed upon not just by open source organizations but also the platforms in which we see CSS in action.

No need to "guess what" anybody here, we're just talking and pitching in our points and opinions, if you come at people like that you'll scare off valuable opinions 😉

I still think that doing math differently is a mistake, is not my decision to make, it'll be what it'll be, and people will use whatever they find the most comfortable and less painful.

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Mar 6, 2019

Using some sort of encapsulation for the arithmetic might be one way to proceed in places where the slash could mean a separator, or any other way, but if we have to teach new users (who probably already program other languages) a different syntax for math, it might become one of those issues that make people look for alternatives.

We do eventually plan to have Sass natively support calc(): #2186. Once that lands, if you do a calculation that's fully computable at compile-time, it will return a number, so you'll be able to write calc($width / $height) instead of divide($width, $height).

There are some very real downsides to making that the only way division works, though. First, encouraging users to use calc() for local calculations can hide errors. Rather than failing, we'd probably produce a plain-CSS calc() for any unit math Sass doesn't understand, so if someone tries to write something like calc(1/2em) they'll end up with a broken property that's hard to debug. Having a Sass-only way to divide is a good way of getting users to express when they expect an operation to be doable by Sass, so Sass can fail when it's not doable.

Second, it means we can't solve this issue until #2186 is completely solved. First-class calc is a very large and complex feature, and designing, speccing, and implementing it will take a long time—especially since we'd need to wait until LibSass supports it to even begin deprecating /-as-division. I don't want to speak for @xzyfer, but I'd guess that LibSass's development time is going to go almost entirely towards the module system for the next six months at least, so that means it may be as much as a year before we could even begin to address this issue.

@fedfigca

This comment has been minimized.

Copy link

fedfigca commented Mar 6, 2019

There are some very real downsides to making that the only way division works, though. First, encouraging users to use calc() for local calculations can hide errors. Rather than failing, we'd probably produce a plain-CSS calc() for any unit math Sass doesn't understand, so if someone tries to write something like calc(1/2em) they'll end up with a broken property that's hard to debug. Having a Sass-only way to divide is a good way of getting users to express when they expect an operation to be doable by Sass, so Sass can fail when it's not doable.

Your points about doing it all with calc() are very true, doing something different like math() (or any better name you can think of) and allowing that to take the usual math syntax in a similar way of what calc takes might be a better way than to have something to just divide, and letting Sass know that it should take over the calculations instead of producing calc() values

@Cleecanth

This comment has been minimized.

Copy link

Cleecanth commented Mar 6, 2019

I'm sure I'm leaving out a ton of complexity, but I second the idea of using a "fenced"/psuedo-function for Sass-specific math (something like math(2/3)).

It feels cleaner than eliminating a common character for math functions and it makes for a pretty straight-forward upgrade path.

Having written some fairly complex Sass over the past 6-or-so years, I've tended to gravitate more and more towards clarity over convenience, though, so I'm a bit biased. At the same time, a wrapper around Sass-specific math still feels like it maintains the spirit of both languages (CSS and Sass) while still being pretty basic to write.

I also realize this isn't one of the original proposals, so it's very possible there's a good reason not to do it. In which case, I could probably get behind using ~ (or really any other character[s]).

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Mar 6, 2019

Something like math() definitely seems tempting once you understand all the ins and outs of the problem, but I suspect for a user who's not super familiar with Sass (and possibly not with CSS either), it's likely to be very confusing. It's not obvious from seeing math() how it's different than calc() (especially once calc() supports a superset of its behavior), why you need to use it for division and not other arithmetic operations, or even whether it's a Sass feature or a plain CSS feature.

It occurs to me that it may be possible to mitigate (although probably not eliminate) the set of invalid calc()s that could be generated. CSS clearly defines what it means for a calc()'s type to be valid or not, so we can eagerly fail if someone attempts to write calc(1/2em). This leads me to believe that the best long-term solution for writing division using / is to do it within calc().

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Mar 19, 2019

I've brought everyone's concerns to my user team, and we've affirmed that we want to proceed using divide() without waiting for calc() support or adding an interim math() syntax. However, we will wait to enable with the deprecation until we have a migration tool in place to make it easy for users to update their stylesheets.

We've also decided on a more concrete timeline. We'll launch the deprecation as soon as possible after the migration tool is implemented, then land the removal in a major version release no earlier than three months after the launch of the module system. We want to ensure a healthy overlap of module-system-supporting versions and division-operator-supporting versions so users can opt to migrate to the module system without migrating away from the division operator.

@metavurt

This comment has been minimized.

Copy link

metavurt commented Mar 19, 2019

Hey @fedfigca - apologies for not responding earlier - thank you for calling me out on my attitude that is not a benefit to the community. Hoping the decision to move forward is a good one. Peace.

@nex3 nex3 changed the title What do we do about forward slash? Stop using / as division Apr 1, 2019

@nex3

This comment has been minimized.

Copy link
Contributor Author

nex3 commented Apr 18, 2019

As a small adjustment, I think we should also make the divide() function accept string arguments and do the same thing the / operator does with strings now (create an unquoted string containing /). It should definitely emit a deprecation warning when a string is passed, but allowing strings will make automated migration much easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.