Less.js: How to handle Maths

Created on 17 Feb 2014  ·  102Comments  ·  Source: less/less.js

  1. We decided on strict maths going forward but there is general unease about forcing () around every calculation
  2. I don't think we want to change things massively or go back to the drawing board

See #1872

Possibility to add another case for calc which like font, with strict mode off, essentially turns strict mode on for a rule ? Too many exceptions in the future?

@seven-phases-max :
Well, there're a lot of other possibilities, e.g. ./ or require parens for division (e.g. 1/2->1/2 but (1/2)->0.5) etc...
Also, the "special cases" (e.g. properties where x/y can appear as shorthand) are not so rare (starting at padding/margin and ending with background/border-radius and eventually there can be more) so we just can't hardcode them all like it's done for font (and because of that I think that the current font "workaround" is just a temporary and quite dirty kludge that ideally should be removed too).

feature request high priority

Most helpful comment

To simplify / restate the proposal - Math changes would be as follows

  1. Addition and subtraction would only be calculated on like units. e.g. 1px + 2px = 3px, 1px + 1vh = 1px + 1vh
  2. Division and multiplication would only be calculated with unit-less divisors/multipliers. e.g. 10px/2 = 5px, 10px/5px = 10px/5px
  3. Value ratios without units would be treated similarly to # 2. e.g. 1/3 = 1/3
  4. For simplification, expressions with partially invalid sub-expressions can be treated as an invalid math expression and output as-is. e.g. 1px + 2vh / 2 = 1px + 2vh / 2

This proposal solves the following (from this thread):

  • font: 10px/5px and similar (ratio) syntax (no calculation)
  • calc(100vh - 30px) (no calculation)
  • lost-column: 1/3 and similar custom ratio syntax (no calculation)

At the same time, these changes would preserve 99% of typical Less math usage. As well, the existing unit() and convert() functions allow users to cast values into compatible units for math.

All 102 comments

I don't think we want to change things massively or go back to the drawing board

Yes, I suggested to start this only because _if_ we want some "default" strict math in 3.0 we'll have to invent something less heavy than the current one (and a possibility of fixing all the problems without introducing any new --alt-strict-math option seems to be quite unreal because of hidden issues behind the font-like hardcoding solution...).

I hadn't realised that its growing

https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius

:(

I think at the moment I am in favour of initially expanding strictMaths to be

  • Off
  • Division
  • On

and then for 2.0.0 setting the default to be Division

So..

Media Queries - if off, switch to division for sub nodes
Font - if off, switch to division for sub node
calc( - if off or division, switch to on for sub nodes

https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius

Yep, I think they are towards to allowing "shorthand values" (i.e. x/y) in _any_ "shorhand property"...

Another option.. process calc calls but only where the units make it possible e.g
calc( 1% + 2%) => 3%
calc(100% - 10 px) => unchanged

This will fix calc but won't fix #1627 and related stuff.
I mean yes, calc(100% - 10 px) => unchanged could be a solution for the calc but this does not cancel the need for a less-heavy-than-parens-everywhere solution.

If strict maths is being revisited, I'd like to suggest that Less functions like percentage() not require extra parentheses inside them. With strict maths on, you have to double-wrap the argument. This would greatly reduce the possibility of confusion and tough-to-find bugs, since both percentage(16 / 17) and percentage((16 / 17)) would be valid.

@calvinjuarez V2 provides a plugin subsystem where a plugin can add an arbitrary number of functions to the environment and in this sense the core won't be able to decide if such built-in function expects a single value or an expression, i.e. it is allowed to accept 16/17, and then evaluating 16/17 _before_ it is passed to a function would be incorrect (well, roughly speaking - it's a bit more complicated than that internally).

In that context ./ change seems to be my favourite though I understand it would be very dramatic change (if compared to (/), not counting it is also requires a whitespace before ., e.g. 16 ./17 and not 16./17, hmm).

Another thing where less currently breaks valid css is background shorthand:

background: url(image.png) 50%/300px no-repeat;

I think at the moment I am in favour of initially expanding strictMaths to be

Off
Division
On
and then for 2.0.0 setting the default to be Division

Sorry I didn't respond to this before 2.0 was released. I think that's a good instinct. Naked division operators simply conflict with too much of CSS, and will conflict more and more as people use more CSS3 features. And I understand people's pushback to not require parens for all math, since it's not needed in many cases.

@seven-phases-max

it is allowed to accept 16/17, and then evaluating 16/17 before it is passed to a function would be incorrect

Strictly speaking, yes that's absolutely correct. This should not be evaluated BEFORE it reaches the function, especially for a custom one. However, I think it would be smart to allow functions to opt to process arguments like this, if it makes sense. So, percentage would receive 16/17 as a text argument, and then the percentage function could make a call to some helper function to see if the text is math. In the case of percentage, the argument is not ambiguous. A CSS3 declaration is not valid here. So, other user-defined functions could operate the same way: opt to evaluate arguments for valid math equations. Therefore, it's not necessarily true that strict math "forces" a double parentheses in this case. I think to suggest that causes some pushback on the idea of strict math, that it must, as a necessity, be verbose in all cases.

Our --math options could be more like:

  • Always
  • Division (default)
  • Strict

So, we could deprecate --strict-math=true and make it an alias for --math=strict

However, I think it would be smart to allow functions to opt to process arguments like this, if it makes sense.

They are allowed already (just test how percentage(16 / 17) and percentage((16 / 17)) work with --strict-math=on).
Though none of functions uses:

and then the percentage function could make a call to some helper function to see if the text is math.

simply because this way each function would have to have those ~20 extra lines of that extra-helpers-conversion-arg-checking-smart-arg-handling-stuff while the own function code is just one(!) line in most cases.

Each function has to have 20 extra lines? How do you figure?

If percentage already works with single parentheses strict math on, then I don't understand @calvinjuarez's issue. You seemed to imply in your response that single parentheses was not achievable.

Each function has to have 20 extra lines? How do you figure?

That's just a typical exaggeration of: maybe 5, maybe 10, maybe 20... Who would care of exact numbers if real-code/aux-code ratio -> 0. (Taking a pragmatic approach I'd stop at percentage(16 / 17) just throwing a error instead of producing NaN% (like now), and not trying to perform any conversion... Though even this way it's still 4 extra lines of code - well, less or more acceptable I guess :)

My initial reply was implying he assumes this 16/17->(16/17) conversion to be performed implicitly by the compiler itself (and not by the function).


Speaking of options. In a perfect world I would dream there're be no options for this at all (i.e. it should be the one and the only and the rest to be marked as deprecated and eventually removed)... All these extra options make the codebase non-maintainable.. even for a tiny one-liner fix/change the number of edge-cases you have to predict and tests you need to perform grows exponentially... (almost for no reason).

I was just thinking it would be something like:

percentage: function(...) {
  Helper.convertMath(arguments);  // a function that doesn't need it doesn't call it
  // ... the rest
} 

Speaking of options. In a perfect world I would dream there're be no options for this at all

I agree. But we'll need the options in the short term. Especially if we're deviating from strict math and legacy behavior. They can both be marked as deprecated if we perfect a "smarter math" option.

Helper.convertMath(arguments);

arguments is too optimistic.
At best (counting not just percentage - which is sort of useless anyway, but any other function expecting a numeric arg) a minimal requirement would be:

some: function(a, b, c) { 
    a = convert2number(a, "Error message when fails"); 
    // b is not a number for example
    c = convert2number(c, "Error message when fails"); 
} 

But that was not my point really, what I meant is probably something like: while this always is/was possible, nobody bothers to write such code... (with a few limited exceptions like)...

Yeah, I get you.

percentage(16 / 17) just throwing a error

would be an improvement, certainly. (I can't think of any time that NaN% would be a useful output.)

As far as which functions would try to be smart about their arguments, there's no reason to try to anticipate everything. A helper function as @matthew-dean suggests could be implemented fairly simply as feature requests are made and discussed for specific functions to be smarter.

In fact, from the start, I'd say that just the math functions should be smart about their arguments. In fact, aside from min and max, math functions that accept more than one argument would really only need to test the first.

Edit: Actually, just whenever a math function is passed only one argument.

What's the status on this? We're getting complaints that LESS tries to parse invalid CSS properties as math. e.g. lost-column: 1/3; breaks.

It also seems http://www.w3.org/TR/css-grid-1/#grid-template-rowcol won't work with LESS.

Can someone patch this so if someone explicitly wants to use LESS to do division on a property that they need to wrap it in parens or something?

@corysimmons Didn't you just ask this on #2769 and get an answer? o_O

One thing that we didn't discuss when this went around the first time. It seems that the only real math conflict is division. And division is essentially an issue because we essentially are "repurposing" a CSS "separation" character.

Rather than try to "wrap" division in any number of ways, or wrap all math (as our first solution to this problem) I wonder why we never talked about the obvious: _not repurposing an existing operator in the first place_, especially when it so clearly a) makes Less code ambiguous, b) causes conflict.

After we've had time to digest this, wrapping math in parentheses, even if it's just division, _still_ causes ambiguity for the cases where the math is already in parentheses. (Which could have also meant that parentheses was a wrong choice of "math wrapper".)

So why not deprecate / as a division operator? I know that it's a common division symbol, but there are other syntax trade-offs Less has done to extend CSS. And it's clear to me now that we're basically trying to work around a problem that is created by Less in the first place by using the single slash. We're creating ambiguity, and then attempting to reverse the ambiguity.

To be fair, the other math symbols are all repurposed in other parts of CSS, it's just that their usage in math doesn't cause any ambiguity.

I was going to propose some ideas, but, lo and behold, there are already standard alternative characters to division. Other than ÷, which isn't on the keyboard, I found this:

The division sign is also mathematically equivalent to the ratio symbol, customarily denoted by a colon (:) and read "is to." Thus, for any real number x and any nonzero real number y , this equation holds:

x ÷ y = x : y

In other words:

lost-column: 1/3;   //  value
lost-column: 1:3;   // division 

I know it would take a bit of parser tweaking to use a colon in math, but it _is_ mathematically sound, apparently. I can't think of other symbols that would make better substitutes. Maybe | as a second choice? It would be more arbitrary though.

Thoughts?

_Alternatively_, we could still support the division symbol within parentheses and/or brackets, as in:

lost-column: 1/3;   //  value
lost-column: 1:3;   // division 
lost-column: (1/3);
lost-column: [1/3];

Yep, that's why initially I started with ./ (inspired by Matlab vector-div operator, it's two syms but visually it's probably the least heavy because of light-weight dot, though in context of Less spoiling the dot is not a brilliant idea).
: - This will be pushing daisies once more, this symbol is used in too many CSS contexts. In fact there's already conflicting syntax:

.foo(@a: 1, @b: 2) {
  result: @a @b;  
}

bar {
    @b: 4;
    .foo(@b:3); // would mean both @second-parameter:3 and 4/3
}

@seven-phases-max Ah, yes, damn. IF ONLY KEYBOARDS WERE LARGER. Yes, a 2-character sequence for division seems inevitable. It had been a long time since I read through the whole thread, so I forgot about that. ./ It's really not bad. If both you and @lukeapage are on board, that wouldn't be a bad compromise. Maybe we move that into the proposal stage, and see if there are any objections?

Reading back, to this point:

not counting it is also requires a whitespace before ., e.g. 16 ./17 and not 16./17

Not sure I agree. Yes, 16. is a valid number, technically, but it would be bizarre for someone to write it that way. I think no matter how you would write it, white space or no, it should divide 16 by 17.

There won't be a perfect option, but I think it's better than a) Using / for division by default, b) always requiring parentheses. (Despite my positions in the past, I've also come around to yes, that's kind of annoying, especially within other parentheses. And especially because it's only required because of division, to my knowledge. Yes?)

I think no matter how you would write it, white space or no, it should divide 16 by 17.

Yes, indeed.
Though thinking of it more I expect ./ to put some additional trickery to the parser (number parsing will need extra look ahead to stop before ./ while currently it always eats ending .).

And especially because it's only required because of division, to my knowledge. Yes?)

Yes, exactly. Not counting the code inside calc - but for this one I guess the solution suggested by @lukepage should do the trick.

(Technically, in future it could be some additional ambiguity for +, -, * if they stay at their CSS color manipulation functions syntax - but this should not be too dramatic since the values there have * 20% form (thus they clearly can be detected as some non-Less-evaluated expr). After all those funs will need some parsing changes anyway since currently values like (* 20%) cause parsing error).

Maybe... we've been going about this all wrong. (Definitely including me in that we, since I was an initial avid supporter of the "strict math" feature.)

We're trying to make math work universally, but.... it doesn't really need to. That is, we're trying to do math in cases where it should be obvious that no math should be performed.

The calc(100% - 10px) example is the most obvious one. There's no Less calculation that can/should be performed unless we cast units, which I agree Less should stop doing, by default.1

Let's look at the font shorthand property used as an example.

font: italic small-caps normal 13px/150% Arial, Helvetica, sans-serif;
font: italic bold 12px/30px Georgia, serif;

The current workaround for this is to turn strict-math: on for the font property. But... why should Less do any calculations in the first place? Sure, 13px/150% is a valid statement in mathematics, but is it reasonable to treat it as valid in Less? 12px/30px you _could_ treat as a valid math statement, but should you?2

That is: in Less, math operations on units should be performed by _integers_ and _floats_, not units. We're treating these as valid math statements, as if people are actually writing their math this way. But I don't see how that's possibly true.

That is, it's reasonable to require someone to write 13px/150% as 13px/1.5. And even ignoring the fact that 12px/30px wouldn't make sense as a math operation, we don't need to know it isn't, and we don't need to white-list font. If a Less author were doing a math operation, they would reasonably be writing it 12px/30. Not only would that be reasonable, it's an extremely high likelihood that's _how they're writing it in the first place_.

It's commonplace for me to write a mixin or even to use something like this within a single block:

width: @size / 2;
height: @size / 2;

Why would I write it this way? Is _anyone_ writing it this way?

width: @size / 2px;
height: @size / 2px;

The operation _sort of_ makes sense if @size is a px unit, but, regardless, it makes less sense in the realm of LESS/CSS to try to do math in the latter case when the divisor is a value with a unit. The second one doesn't look like math. It looks like a CSS value.

If we stopped doing math operations for multiplication or division (just division needed for ambiguity, but multiplication also for logical consistency) when the multiplier or divisor is a value with a unit, I think this problem would largely go away. In contrast, units _are_ logical for addition and subtraction, but probably don't need to be required (which is how it is now).

@pad: 2px;
width: 10px + @pad; 

But when adding / subtracting one value w/ unit to another w/ a different unit, Less should leave it alone.

@val1: 100%;
@val2: 10px;
width: calc(@val1 - @val2);

// output
width: calc(100% - 10px);

Requiring matching units for addition / subtraction (or integer / float), and requiring integers / floats in math operations against units (no units multiplied / divided by units) would solve 99% of ambiguities, and still allow valid math operations without any new symbols.

For example, based on those rules, the background shorthand would be fine:

background: no-repeat 10px 10px/80% url("../img/image.png");

% is a CSS unit. px is a a different CSS unit. Therefore, no math. The special exception would be a zero.

background: no-repeat 0 0/80% url("../img/image.png");

If someone appears to be dividing zero by another number, or dividing a number by zero, I think we can safely just output as-is.

Border-radius, same thing:

border-radius: 30% / 20%;

Less would give it a pass. If someone intended to do math, the appropriate way to write it would be:

border-radius: 30% / 0.2;

The nice thing is, by making these distinctions, it should be obvious that a math operation _cannot_ be a CSS value, since, like the border-radius example, a valid CSS value would require units on both sides. There would be no overlap / ambiguity.

Exceeeept one case I can think of, and there may be others:

font: 10px/1.5;

You can (and usually should) represent line-height as just a number. (But also probably shouldn't use font shorthand.) But if we've reduced it to one case, that's pretty good. Turning on strict math for font (except for inside functions within the font value) is an okay solution. (I'm not sure there isn't a better one, but it works.) And still is the lowest-hurt solution, since those font shorthand values usually appear in imported UI style libraries, CSS resets, or custom font style sheets.

So, there's still a use for strict math, but I think with these changes, less so, and maybe not needed as an option in the future.

I welcome the responses / comments of the gallery.

1 _I realized I should make it clear that @lukeapage's comments, and @seven-phases-max's link to that are what got me thinking in this direction, to just take it a step further._
2 _As I figured out while looking at examples, turning strict-math on for font may be an unavoidable solution, but I think the rest of the logic applies._

Just for some historical context, it's important to note that casting units when Less.js was first released wasn't as much of a problem. calc() wasn't implemented, and background and border-radius did not have slashes as valid values. I think font was the only place that tripped things up (which, ironically, may still be the case).

My only concern is the implementation side, e.g.: for calc(4px + 2rem + 20%) (actual units do not matter), the first addition results in a not-anymore-numeric result, thus the second addition handler can't distinguish its input from whatever not-a-number + 20% statement where it should throw a error. Not a big problem probably (solvable by putting extra type/type-flags), but still needs some investigation.

And the other concern is ehm, not sure, "redabilty"? I.e. while for explicit numbers the result is looking crystal clear, for variables it starts to look quite ambiguous, e.g.: border-radius: 10px @a / @b 30px; - you never know what this is supposed to mean until you see @a and @b definitions.

And yes, font still remains a problem (likely other shorthand-properties seem to use units on both sides but again we never know what they come-up with next (also counting things like #2769)... A few more goodies like font and it'll start to look broken again).

P.S. One more issue (a minor regression maybe).There're values like:

border-radius: 10px / auto;
border-radius: 1px inherit / 2px;
background: ... center / 80% ...;
// etc.

I.e. for the whole thing to work we'll have to disable any current incompatible-div-operand-errors so that any foo/bar, 1x/bar and foo/1x can pass w/o a error.

My only concern is the implementation side, e.g.: for calc(4px + 2rem + 20%) (actual units do not matter)

This is what I'm saying. Actual units should matter. Less should leave that alone, regardless. 4px + 2rem has meaning in the browser, but is meaningless in Less. There's no reason to try to add it when it makes no sense, and causes these add-on issues.

e.g.: border-radius: 10px @a / @b 30px; - you never know what this is supposed to mean until you see @a and @b definitions.

This is a case where we can't save the style author from themselves (same with the first example, if someone actually was trying to add rems to pixels for some reason). I'm sure there are all sorts of existing examples where someone could write their LESS code to be confusing to follow. That exists anywhere.

With these math rules I'm proposing, consider:
calc(4px + 2rem - 2px)

Less could/would calculate that as calc(2px + 2rem), which is actually perfectly fine, and is actually a correct value output. Right now, Less calculates it as calc(4px), which is not a correct or useful answer. (Yes, it's currently correct if we drop units, but units are not meaningless, and except for a few cases, not interoperable.) Less calculates 2px + 100% as 102px, as if there's some value in that result, when no one would possibly want that as the result.

for the whole thing to work we'll have to disable any current incompatible-div-operand-errors so that any foo/bar, 1x/bar and foo/1x can pass w/o a error.

I think that's actually the sanest way to treat it. Like functions, if there's not a Less result, then it should pass through. Since / is a valid separator for more than one property's value, Less can't know that it _isn't_ valid, unless we'd add whitelisted values for particular properties. (Nope.) At that point, the result isn't tragic. If a pass-through value is invalid in the browser, it's visually apparent. It seems better to try to pass the value along to the browser in case it's valid, than throw an error because Less isn't sure.

Less could/would calculate that as calc(2px + 2rem)

I'm afraid it never will as this will require a brand-new optimizing expression handler which would an overkill (basically 4px + 2rem - 2px is stored in the tree as (4px + 2rem) - 2px so to get 2px + 2rem it has to be a re-ordering engine to try all valid shuffling (trivial in this particular case but becoming quite complex for more operand/operators). But never mind, leaving 4px + 2rem - 2px as-is is fine (after all if you do 4px - 2px + 2rem it is optimized).

But what I actually meant with that remark is the thing similar to:

so that any foo/bar, 1x/bar and foo/1x can pass w/o a error.

I.e. (4px + 2rem) - 2px for the expr.evaluator would be similar to foo + 2px, thus to work it shouldn't generate errors for that kind of things too.
Actually, I tested foo/bar, 1x/bar, foo/1x and they already pass w/o errors (strange I thought they do throw), but other operators result in all sorts of weird things (nothing really critical though, just a matter of fixing each case one by one).

I'm afraid it never will as this will require a brand-new optimizing expression handler which would an overkill (basically 4px + 2rem - 2px is stored in the tree as (4px + 2rem) - 2px so to get 2px + 2rem it has to be a re-ordering engine to try all valid shuffling (trivial in this particular case but becoming quite complex for more operand/operators).

Why shuffling? You flatten operators at the same precedence level (so the tree is Sum(4px, 2rem, -2px)), collect terms with compatible units (perhaps by normalizing units beforehand), and simplify each part. It's symbolic algebra, where the units are treated as independent variables.

I'd like to write it myself, but there are plenty of libraries out there which are probably better-tested and more likely to be complete. I'd bet some open-source optimizing compilers written in Javascript have such simplification subsystems.

Point is, while this is not an easy problem to solve, the more general problem is already pretty well solved, and such a subsystem could be useful for Less.js in other ways.

You flatten operators at the same precedence level (so the tree is Sum(4px, 2rem, -2px)),

And what exactly there makes the code to think some expression tree is actually flattenable at all? (So my "suffling" there is not about some specific deduction algorithm, but a shortcut for "try all of them" including your "flatten").

but there are plenty of libraries out there which are probably better-tested and more likely to be complete.

I'm not sure if you're serious. So you think that all that code to convert Less tree to an external lib tree and back (not counting none of those JS libs can handle CSS units) is actually worth that specific 4px + 2rem - 2p case to be optimized? Hmm...

So, no problem at all (I did not say it's impossible just that it will never ever be worth it) - try and PR is welcome.
(Also just in case it's probably important to note that the subexpression optimization thing is not even the goal of this ticket, not even in its top three).

And what exactly there makes the code to think some expression tree is actually flattenable at all? (So my "suffling" there is not about some specific deduction algorithm, but a shortcut for "try all of them" including your "flatten").

The parser determines whether the context is e.g. "length", and whether the subtree can be interpreted as "length".

I'm not sure if you're serious. So you think that all that code to convert Less tree to an external lib tree and back

It needs to be parsed into a syntax tree. From there, it would be relatively simple to output a string representing an algebraic expression. Going back might be harder: it might need to be parsed again from a string, or yes, it might need to take the external lib's tree. The difficulty depends on which library is chosen.

(not counting none of those JS libs can handle CSS units)

You'd just convert units to algebraic variables. Substitutions (e.g. mm -> px) can even be done while the expression is in symbolic form.

is actually worth that specific 4px + 2rem - 2p case to be optimized?

I'm making an alternative suggestion for expression optimization which is less difficult (as an algorithm problem that Less's own code must solve) and more generally-useful than what you said was necessary.

Algebraic simplification can resolve things with parenthesized subexpressions, and allow Less to add more mathy features.

try and PR is welcome.

I will try. I only started looking into Less today, and I have problems finishing projects, so I admit that I probably won't get a PR out.

Also just in case it's probably important to note that the subexpression optimization thing is not even the goal of this ticket, not even in its top three

I know. I was here for one of the reasons. Why such bite?

Why such bite?

Well, maybe... If so - my apologies (It looks I'm just too shocked of efforts and time someone is ready to dedicate to solve pretty much not existing calc(4px + 2rem - 2px) problem. Probably we just have very different notion of lightweightness).

and allow Less to add more mathy features.

Could you name a few?

to solve pretty much not existing calc(4px + 2rem - 2px) problem

Huh? Less can't handle that at all. [rest deleted as I mis-understood what was being addressed]

To simplify / restate the proposal - Math changes would be as follows

  1. Addition and subtraction would only be calculated on like units. e.g. 1px + 2px = 3px, 1px + 1vh = 1px + 1vh
  2. Division and multiplication would only be calculated with unit-less divisors/multipliers. e.g. 10px/2 = 5px, 10px/5px = 10px/5px
  3. Value ratios without units would be treated similarly to # 2. e.g. 1/3 = 1/3
  4. For simplification, expressions with partially invalid sub-expressions can be treated as an invalid math expression and output as-is. e.g. 1px + 2vh / 2 = 1px + 2vh / 2

This proposal solves the following (from this thread):

  • font: 10px/5px and similar (ratio) syntax (no calculation)
  • calc(100vh - 30px) (no calculation)
  • lost-column: 1/3 and similar custom ratio syntax (no calculation)

At the same time, these changes would preserve 99% of typical Less math usage. As well, the existing unit() and convert() functions allow users to cast values into compatible units for math.

Less can't handle that at all.

You just did not read what me and @leewz were talking above. It had nothing to do with calc(100vh - 30px). And my "to solve pretty much not existing problem" goes solely to "optimizing arithmetic expressions in calc".

@seven-phases-max Ohhh right that. Sorry. No, we don't need to optimize. Just figure out when to math.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Removing the slate label since the problem is still important and getting only worse as CSS evolves.

@matthew-dean
Going to play devil's advocate for a moment here...

12px/30px you _could_ treat as a valid math statement, but should you?

Yes; you should. It computes a scalar value that represents a ratio between both sizes and that can be quite useful when converting to the em unit.

Let's say I have a known base font size of 16px and a known heading size of 24px, both tucked away in variables, and then I want to assign a particular flavor of heading with the correct font size, but in an em unit.

@fontsize-body    : 16px;
@fontsize-heading : 24px;

// ( ... and then somewhere else ... )

.heading {
  font-size : unit(@fontsize-body/@fontsize-heading, em);

  // Or if dividing compatible unit values is produces a unit-less scalar
  // value ( as it really _should_ -- mind you... ),  you could prefer:
  font-size : @fontsize-body/@fontsize-heading * 1em;
}

And if you need to support division of compatible units as a math expression, then you will continue to need to support a way to disambiguate literal CSS from Less math expressions to cover each half of the cases.

Now granted, that could involve using parens to force a math expression and taking a CSS literal by default. But that seems wrong and error-prone. It requires a lot of pre-cooked special cases such as in border-radius and font shorthands, and requires that Less keep itself continuously up-to-date on these. (As has been illustrated by some of the new grid properties developing the same problem with the division sign.)

So...

Why not flip your reasoning around? If something looks like a math expression and it has compatible units then it will be treated as a math expression by default. And if you do not want that to happen ... well; then use the ~"..." escape hatch. It's exactly what it's there for, after all...

My current vision of the proposal:

  • Stop treating / as division anywhere regardless of any untis and regardless of any options (unless - optionally - it's enclosed by redundant parens like with -sm=on)
    Thus only 1anything./3whatever and (optionally) (1anything/3whatever) are evaluated by Less.
  • +, - and * remain unchanged
  • calc subissue is solved by not-evaluating any arithm. expressions inside calc(...) (though, yet again, redudant parens may have their effect too).

@seven-phases-max

The problem with that is / is incredibly ingrained as the division operator. Using any other symbol for it would be a tough sell to users. You're taking the general use case and throwing it out for the exceptional use case (/ in CSS shorthands) that hardly anyone ever uses.

@rjgotten

The problem with that is / is incredibly ingrained as the division operator.

Not in CSS.

for the exceptional use case (/ in CSS shorthands) that hardly anyone ever uses.

By now / is already used a lot, and in comparison it's the Less div op is totally much more exceptional than CSS / these days (unlike it was just a few years ago where CSS / could mostly be found within font only).

@seven-phases-max Thanks for keeping on top of it. I added the Stale bot to help us manage issues and gave a pretty generous amount of time for marking stale. I also exempted two labels, "bug" and "up-for-grabs". Any suggestions on that are welcome, such as other labels to whitelist. File is here: https://github.com/less/less.js/blob/3.x/.github/stale.yml

Getting back to the issue thread...

@rjgotten
Your use case creates an arbitrary problem. The argument is that it's useful. But structuring your vars that way creates a problem that's avoidable with syntax that is, as @seven-phases-max pointed out, ambiguous.

Even taken at face value, with CSS as the guide for Less principles, you cannot, in calc, divide 12px by 30px. Doing so in Less not only creates an ambiguity problem, but it breaks from the model for no good reason (other than historical). Probably one of the things that's missing from Less is just a way to extract the numerical value of a unit-value so that Less doesn't have to do this math magic for division.

So, the simple answer is that your example would look something like:

font-size : @fontsize-body/number(@fontsize-heading) * 1em

But I'm okay with @seven-phases-max's proposal as well. We could still evaluate _vars_ in calc(), but not math. And not evaluate division outside parens. I think it's a good compromise. And, possibly, if you wanted to preserve magic division math of dividing a unit by a unit but keeping the unit (which is still weird, but that's fine), it could happen within parens.

I know there are a few different preferences on the subject of math in Less, but I think it would be really great if we could reach a consensus on something that caused the least side effects and was the easiest to reason about without causing a large maintenance or development burden.

I think we're all on the same page that the current (default) approach to math in Less is broken. And we'd received the feedback that parens-everything as a default was easier to reason about, but burdensome. So I'm hoping for some good middle ground that we can put in as a default soon (probably in 4.0?)

Probably one of the things that's missing from Less is just a way to extract the numerical value of a unit-value

The unit function actually does that if you don't specify a second parameter. But that's more a side-effect of the fact that scalars are currently implemented with a Dimension node type that doesn't have a unit defined.

Granted; it goes the long way around, but if you _really_ want to avoid dividing dimensions that have compatible units, then tearing the unit off would work.

I'd also add that I'm strongly against any "compatible units" guessworks (like let a/b remain to be a division and b/c to be not) in this particular context.
Simply because:

  • exceptional handling is the root of all evil (e.g. #3047 - see below, http://stackoverflow.com/questions/19705791- I recall I could not find the reason for the SO issue until I actually stepped through every of the zillion lines of the involved Less code in a debugger).
  • and most important, it's just not future-proof, i.e. if a major breaking-change is unavoidable we'd better to ensure to make it once (and ideally forever)... and not like they add some new CSS feature involving a/b and it's broken again.

So for me "Compatible Units" stuff is more like totally unrelated and orthogonal thing (there could be some discussions on resulting units with or without -su, but that's another story).


(offtopic:)
And btw., speaking of above examples:
@rjgotten if you have:

@fontsize-body/@fontsize-heading * 1em; 

somewhere in your code and either of two variables is px you actually using a bug :)
The proper code is:

1em * @fontsize-body/@fontsize-heading;

This always results in a well defined unit per http://lesscss.org/features/#features-overview-feature-operations (counting #3047 to be fixed, the bug is yet again an example of a bug produced exactly by too much guesswork code around "compatible units" in the code base). No real need for --su, number, unit bla-bla... Current -su behavior like "just throw a error if you see incompatible units", i.e. a simple validation, is more than fine (for me). I can't see any need for 1px/2px->.5 and 1px*2px->2px^2 mambo-jambo overengineering. But yet again this is another unrelated story).

@seven-phases-max

actually using a bug

Uhm.. yeah; you're right, ofcourse. Luckily I don't actually have that code in production anywhere. It was just a quick example tossed together to illustrate the point.

I'd also add that I'm strongly against any "compatible units" guessworks (like let a/b remain to be a division and b/c to be not) in this particular context.

I think I was on board with that more as an olive branch to those that want naked divisors. But I think that your comment [here] is the most workable proposal. It was the original intention of "strict math", to eliminate ambiguity. I think the concern became operations within mixin and function calls leading to all sorts of parens within parens. But, in general, I'm with you that the efforts to make math easy in Less have also, ironically, made it very difficult, because of the increased ambiguity.

Also, I later realized that font: 10px/3 is valid shorthand. So there really is no algorithmic solution that can help there.

To get back to your question, @rjgotten...

Why not flip your reasoning around? If something looks like a math expression and it has compatible units then it will be treated as a math expression by default. And if you do not want that to happen ... well; then use the ~"..." escape hatch. It's exactly what it's there for, after all...

The idea/relationship of Less to CSS is similar to TypeScript to JavaScript. That is, rename your valid .css to .less and you can start adding Less features. Similar to how you can rename .js to .ts and start adding TypeScript features. If you don't add anything, you should get output the same valid Less / JavaScript, because the languages are supersets of the base language.

However, in the case of these ratios or other / divisors in CSS, Less already fails right out of the gate, by default. Your regular CSS arbitrarily becomes a Less math expression with a different result, even though you've changed nothing. That violates the contract of the language. Being able to change your .less into an entirely different Less expression in order to get back the original CSS you started with misses the point. That work should never be required. Less should not require you to change your valid CSS into compatible Less expressions in order to get back the valid CSS you had in the first place. That's just a broken model.

The same reasoning applies to calc(). Yes, you can string-escape your expressions, but you shouldn't have to. .css renamed to .less should produce the same effective CSS. This should be the base goal of the project, to not interfere / overwrite / over-interpret the original stylesheet. Anything else is trying to push the problem onto the developer for no other sin than using the parser/language in the first place.

Yes, you can string-escape your expressions, but you shouldn't have to. .css renamed to .less should produce the same effective CSS. This should be the base goal of the project, to not interfere / overwrite / over-interpret the original stylesheet. Anything else is trying to push the problem onto the developer for no other sin than using the parser/language in the first place.

That.

LESS knows where the / is allowed and where it isn't in css. Where it isn't but appears anyway, it should assume maths to be done. Also where maths is surrounded by rounds brackets where rounds brackets aren't allowed in css should be interpreted by LESS.

In other words, LESS should try to interpret as little maths as possible to get to valid css. As soon as the output it valid, stop executing any more maths.

@thany

Too many assumptions of what the compiler knows (and some of them are simply wrong).
Either way "the round brackets" mode is nearly what --sm=on does, so use that and forget about this thread (most likely you just not using any math in your projects beside calc (introduced a few years after Less is designed) so you can't see how the extra parens are annoying. But others do.)


For the rest see https://github.com/less/less.js/issues/1880#issuecomment-345194431.

@seven-phases-max Don't forget that the / also has meaning in background and border-radius shorthands, and possibly others I'm not thinking of right now. LESS will happily treat those as a division. With or without strict math mode, LESS should "know when to stop" doing its own little maths.

@thany
LESS should "know when to stop" doing its own little maths.

No, it shouldn't. There is no way to know where future CSS shorthands with the division slash are going to appear. Having the Less compiler 'know' about that is a fundamentally flawed approach and the very thing this discussion is attempting to find a solution to.

Sofar there are two, sane and predictable choices for / as a division operator:

  • Always treat it as such and require explicit escaping for other uses.
    This will break the behavior where Less syntax is a strict superset of CSS syntax.
  • Never treat it as a division operator, __unless__ it is within a known math context.
    Where known math context could/would be decided as in the current --strict-math=on behavior.

(Also; please note that the Less name is no longer spelled in all caps.)

Don't forget that the / also has meaning in background and border-radius shorthands,

This is basically what this thread starts with.
And see the summary of the proposed changes at https://github.com/less/less.js/issues/1880#issuecomment-345194431 (w/o any assumptions of what compiler should or should not know).

No, it shouldn't. There is no way to know where future CSS shorthands with the division slash are going to appear. Having the Less compiler 'know' about that is a fundamentally flawed approach and the very thing this discussion is attempting to find a solution to.

I agree with this completely. @thany While you agreed with me in what you quoted of what I wrote, you're arriving at a different conclusion than I would make. Less shouldn't and can't know when to "stop doing maths". What I would say is that the breaking change should be that Less is more conservative about starting math (to use the American/Canadianism) in the first place.

@rjgotten

Never treat it as a division operator, unless it is within a known math context.
Where known math context could/would be decided as in the current --strict-math=on behavior.

Just to clarify, this part (which I agree with):

Never treat it as a division operator, unless it is within a known math context.

Actually has 4 possible solutions, which I know you know, but just re-summarizing for the thread:

  1. Do all math only in parentheses. This has apparently been rejected by the commons, which is fine, although it's still available as an optional toggle (strictMath).
  2. Do all math everywhere, but division only in parentheses.
  3. Do all math everywhere, but division only in parentheses. Unless the division operator is printed as ./
  4. Do all math everywhere, but fix math so that 12px/4px does not result in division. (Multiplication and division only with unit-less values.) In other words, division everywhere, but with much more conservative rules. In the cases where it doesn't solve things, resort back to escaping. So, not a complete solution, but still an (arguable) improvement over the current situation.

From a usability perspective, I like making math "smarter" as in #4. From an engineering and maintenance standpoint, and to protect against future CSS changes, I like #3 as the most robust solution.

However, I think in reality, we would need to do both #3 AND #4 in order to fix calc(). Right now, Less ignoring all units when doing math is a real mess. 100vh - 12px should never be touched by Less (parentheses or not). But IMO neither should 12px/4px (parentheses or not), but I might be in the minority on that one.

So, I don't see this so much as a "math conflicting with CSS syntax" problem so much as Less being way overly aggressive with prematurely solving math equations to begin with.

Multiplication and division only with unit-less values.

it's not going to do the trick since there're things like:

font: small-caps bold 24px/3 ...;
lost-column: 1/3;
// etc.

and they are not divs.

However, I think in reality, we would need to do both #3 AND #4 in order to fix calc()

calc() is a pain. Ironically it is _probably_ best solved by implementing it as an actual Less function, that would ideally take its parsed expression tree and attempt to simplify the expression. That is: it should pre-compute and combine compatible components such as 4px + 12px (or 4px + @a when @a is known to have a pixel value) but leave incompatible components, e.g. those with incompatible units, alone.

E.g.

@a : 4px;
@b : 2;
width : calc(100%/@b - 10px + @a);

should eventually render

width : calc(50% - 6px);

(repeating myself from https://github.com/less/less.js/issues/1880#issuecomment-345345735)
And I do not see any benefit of vastly overenginneering the complier in order to optimize the expressions inside calc. If you're writing calc then you're fine with the browser to do the job whatever long expression you have there. So as already mentioned above I'm for "don't touch anything inside calc" (= don't try to be smarter than it's really necessary) and leave it for browser (or for a css-minifier since, if I recall correctly, some of them already do pretty good job on optimizing calc subexpressions).


The "smart unit behaviour mamabo-jambo" reminds me of the min/max monsters (bloating stack of non-maintainable and never ever used code) - how much I regret I did not scream against the "units mambo" back then, oh (So I'll keep whining here until the very idea of "different operator semantics depending on the operand units" is totally annihilated :P).


P.S. As soon as calc becomes a function receiving a non-arithm-evaluated expression (it will have to anyway) one will be able to write a plugin and override it with whatever optimization desired.

@rjgotten
Having the Less compiler 'know' about that is a fundamentally flawed approach

I think it's a fundamentally correct approach. LESS is a compiler to CSS, so it makes all the sense in the world that LESS knows about what it is compiling to.

@matthew-dean
Less shouldn't and can't know when to "stop doing maths". What I would say is that the breaking change should be that Less is more conservative about starting math (to use the American/Canadianism) in the first place.

Interesting you think of it from the other side, so to speak. Here's what I'm proposing:

background: url(...) no-repeat 50% 50% / 40px + 10px 40px;

What LESS should do here, is obvious to me:

background: url(...) no-repeat 50% 50% / (40px + 10px) 40px;

Resulting in:

background: url(...) no-repeat 50% 50% / 50px 40px;

It shouldn't calculate the 50% / 50px part in this, because (1) it shouldn't be able to because of incompatible units and (2) because this is already far enough for a background value. So this is where it'll "stop doing maths".

That's what I meant by LESS "knowing when to stop".

Were it a different property like this:

padding-left: 50% / 10px + 5px;

It should break with an error (incompatible units). One possible output would be 50% / 15px which is invalid for this property. Another outcome could be 5% which it'll currently do, which is just wrong in every direction.
And:

padding-left: 50px / 10px + 5px;

Should result in:

padding-left: 10px;

As expected. So in this case, the / is invalid for padding-left and is is taken into LESS and do its maths thingy.

@matthew-dean
Actually has 4 possible solutions, which I know you know, but just re-summarizing for the thread:

One more:
5) Use the \ operator for divisions in LESS, and deprecate using /. MATLAB has something like this, and certain flavours of BASIC used it to force integer division. So using the backslash is not completely unheard of.

/edit
We can also lobby for including a ÷ key on keybaords and use that as the division operator. That's the one I learned in elementary school :)

What you suggest was already discussed many times before (do not hesitate to look at the threads referenced here). So here're just tiny-lazy comments:

Use the \ operator for divisions in LESS, and deprecate using /.

https://github.com/less/less.js/issues/1872#issuecomment-35245890

enough for a background... is invalid for padding-left

Meet the "Guys, my browser/polyfill/whatever just has added/updated/expanded support for a fnord property, will you please release a new Less version for me?" issue.
Meet the font issue.
etc. etc.
Well, @rjgotten already commented above on why that kind of "knowledge" is the path to nowhere.

@seven-phases-max
Backslash was only a suggestion. You might as well use the ? for division. It doesn't really matter. What I'm suggesting, I guess, is to not use one character (/) that has very different and ambiguous meanings.

Meet the "Guys, my browser/polyfill/whatever just has added/updated/expanded support for a fnord property, will you please release a new Less version for me?" issue.

CSS is a well-defined standard. You shouldn't support anything outside the standard. That's your problem, I feel. You mustn't support the fnord property, because it isnt part of any standard. When this new unspecced property happens, LESS may fall back to its default behaviour, which could be the current, or requiring parentheses, or whatever else as long as it isn't ambiguous.

Meet the font issue.

The font issue proves that the / problem has existed since the absolute beginnings of LESS. Not just when background got the capability to include a value for background-size or when border-radius came about. Tbh, by stating the font issue, you've just made the best argument against yourself :)

Tbh, by stating the font issue, you've just made the best argument against yourself :)

I think you misunderstand something. It was me who initially proposed to totally remove the / as a div operator. So for the rest of stuff I suppose it's the same issue of you not paying attention to what you're replied with.

CSS is a well-defined standard.

If you're going to stick to the specification parts that have reached fully maturity at the TR level, maybe.
Otherwise? No it's not. There are satellite specifications of new CSS 'modules' and revisions of existing modules with new additions in them popping up monthly, if not weekly.

@seven-phases-max

it's not going to do the trick since there're things like: font: small-caps bold 24px/3 ...

No, I get that. My point was exactly that: unit-less only div/multiplication lessens the problem but doesn't solve the problem.

And I think in general your suggestion of ./ for all division still seems quite logical.

@thany Rather than responding to all the specific examples, I'll say that in general, the efforts to make Less math smarter will just kick the ball to a different yard line. It's still the same problems, just in a different spot. As @seven-phases-max has said, nothing you've suggested hasn't been discussed.

And I do not see any benefit of vastly overenginneering the complier in order to optimize the expressions inside calc. If you're writing calc then you're fine with the browser to do the job whatever long expression you have there. So as already mentioned above I'm for "don't touch anything inside calc" (= don't try to be smarter than it's really necessary) and leave it for browser (or for a css-minifier since, if I recall correctly, some of them already do pretty good job on optimizing calc subexpressions).

I agree with this entirely. We would kill 99% of new math-related issues if we just whitelisted calc(). While I've suggested some ways that Less could more smartly do math to ALSO avoid calc() issues (which maybe it should), I apologize if that was interpreted as an argument against this idea. I support this too.

The only caveat (as discussed here/elsewhere) is that a developer will inevitably want to use variables in calc(), so we need to be careful to make sure we continue to swap out vars, but just don't do math. I'm not sure how challenging that is. If we managed to do that, I would support a change in calc() handling today.

@thany

The font issue proves that the / problem has existed since the absolute beginnings of LESS.

This may be true and is a fair point, but there were many suitable workarounds and it wasn't necessarily standard practice at the time to use the font property shorthand. It's really since multiple backgrounds and calc() arrived after Less started that made this more of an issue, and now the CSS Grid syntax means there is now a ton of conflict where initially none existed in everyday practical terms.

Incidentally, this is how Sass resolves the problem, illustrated by this example:

p {
  font: 10px/8px;             // Plain CSS, no division
  $width: 1000px;
  width: $width/2;            // Uses a variable, does division
  width: round(1.5)/2;        // Uses a function, does division
  height: (500px/2);          // Uses parentheses, does division
  margin-left: 5px + 8px/2px; // Uses +, does division
  font: (italic bold 10px/8px); // In a list, parentheses don't count
}

It's not a one-to-one, since you can do math (currently) within arguments to Less functions, AND Less functions can return pixel values (so Less could do font: print10px()/8px, theoretically? no?), so I'm just pasting it illustratively, not as any sort of suggestion. It's just interesting how they approached the problem.

Personally, I think making developers try to remember in which cases math will occur based on the existence of magic expressions is kind of crazy-making (like being pre-pended by a +?), but to each their own.

Personally, I think making developers try to remember in which cases math will occur based on the existence of magic expressions is kind of crazy-making

+1


Not even counting that the "solution" solves none of the problems discussed here beyond what lessc --sm does already. Why would I'll write 0 + 1/2 instead of (1/2) when what I want is just a division? And different results for 1/2 and $var/2 is just a phenomenally brilliant ... ~stupidity~ "Happy debugging, losers!" message.

@matthew-dean
Incidentally, this is how Sass resolves the problem, illustrated by this example:

That's a brilliant solution without resorting to a different operator.
If the expression is possibly valid CSS, leave it.

Personally, I think making developers try to remember in which cases math will occur based on the existence of magic expressions is kind of crazy-making

Disagree. It's a simple set of rules, no different from any other sets of (crazy or not) rules you need to remember.

There are a few edge-cases where maths does occur without meaning to, but then you'll just apply parentheses and you're done.

Not even counting that the "solution" solves none of the problems discussed here beyond what lessc --sm does already. Why would I'll write 0 + 1/2 instead of (1/2) when what I want is just a division? And different results for 1/2 and $var/2 is just a phenomenally brilliant ... stupidity "Happy debugging, losers!" message.

Ha, yes, this.

@thany

That's a brilliant solution without resorting to a different operator.
If the expression is possibly valid CSS, leave it.

Not to get into the weeds, but it's not going to work for Less, for syntactical reasons and also language consistency. It may be brilliant for Sass, which I would say is arguable, but I'm not personally involved with that project from a design perspective, so who's to say. All I know is that the options on the table are the best place to start, and I'm confident if you spent as much time going through some of the message threads related to this issue and spent time with the language, you'd come to the same conclusion.

One observation we cannot get away from: if you import valid vanilla CSS, its output should be functionally identical. So the only conclusion I can draw is that LESS should not touch expressions that are already possibly valid CSS. How this is done, is not up to me. But the fact remains that importing vanilla CSS is unreliable, mostly as a result of LESS executing divisions too eagerly.

Importing vanilla CSS in Sass works virtually flawlessly, because as said, Sass leaves the / operator alone if it doesn't indicate to the compiler to go and do stuff, according to a few simple syntax rules. These syntax rules describe ways to force a division that cannot occur in valid vanilla CSS, and that's why it's brilliant.

You still argue with your own imaginations and not with what they tell you. Answer the simple question: "How 0 + 1/2 can be possibly better than (1/2)?". If 100%-CSS compatibility is the only thing you care set the lovely -sm and forget about this thread.

One observation we cannot get away from: if you import valid vanilla CSS, its output should be functionally identical. So the only conclusion I can draw is that LESS should not touch expressions that are already possibly valid CSS. How this is done, is not up to me. But the fact remains that importing vanilla CSS is unreliable, mostly as a result of LESS executing divisions too eagerly.

This part I essentially agree with, and I think you'd find a lot of agreement in this thread on that point. Most of the disagreements have been around solutions, with each solution having various side-effects.

I'd be on-board with making these breaking changes as a priority:

  1. math outside parentheses requiring ./ instead of a naked /. (And, obviously, function arguments requiring an extra set of parentheses e.g. my-function(12px/10px, arg2, arg3) does not divide 12/10 but my-function((12px/10px), arg2, arg3) does and my-function(12px./10px, arg2, arg3) does) Unless we want to do some other special indicator of / such as \/ or just \ e.g. 12px\10px. 12px./10px still looks a little weird.

@seven-phases-max - I want to revisit the backslash. Related to this, I know you mentioned https://github.com/less/less.js/issues/1872#issuecomment-35245890, but I don't see any outright conflict, since those identifiers are not and I think cannot be part of math expressions. Or am I wrong? I suppose it's possible to come up with a theoretical case like a function name with an escaped character as part of the name, but that seems more like an intellectual exercise than a real-world case. Escaped selectors, yes, those are commonly used for various reasons, but in property values, it just seems unlikely, or, in an edge case, okay to break / workaround with the historical (just escape this text) workaround.

Can we explore a bit more that using a single backslash for division would cause real-world conflicts? My instincts are that \ is less problematic than the current situation around /, and it's more dev-friendly than ./. If we did the research and found it was feasible, then we could make the breaking change to use it everywhere, instead of context-switching allowing / in parentheses. And then we could support / with a legacy switch.

  1. (second priority) calc() being special-cased so that it can do variable replacement but will not evaluate any math expressions

Can we explore a bit more that using a single backslash for division would cause real-world conflicts?

Well, the problem that \anycharacter is valid CSS and has its own meaning. Sure, you barely find such code in real projects (except maybe the ie \9-like hacks), but... do we want to feed that lion? Fixing one "uh-oh, my 100%-valid CSS does not compile" by introducing another "uh-oh" of the same sounds a bit weird :)

As for calc I guess we had a consensus from the very beginning - so it's basically only waits for a volunteer to implement it (I estimate the quick fix to be done in just 5-6 lines of new code - so it's really only about a brave man to do this - minor implementation details may vary but I think they are fine to be decided in process).

Well, the problem that \anycharacter is valid CSS and has its own meaning. Sure, you barely find such code in real projects (except maybe the ie \9-like hacks), but... do we want to feed that lion? Fixing one "uh-oh, my 100%-valid CSS does not compile" by introducing another "uh-oh" of the same sounds a bit weird :)

I hear you, it's a possible trade-off. And yeah I know that \anycharacter is valid CSS. I guess I'm wondering if it's a better set of trade-offs. It's just that when I wrote out 12px./10px it just felt weird, syntactically. I feel like Less syntactically has tried to re-purpose CSS as much as possible without creating conflicts.

Like, ./ provides syntax clarity and completely avoids conflict, but so did enforcing parentheses everywhere for math, which is why I supported it, but there was backlash, and I'm worried about that here. So is there any legitimate case where we would mistake 10px\10 as the developer's intent to escape \10? I know that's a hard theoretical, and your point would probably be that we don't really know.....It's a complicated question, and adding new syntax is always fraught, especially with Less since we don't know all CSS in the wild nor future CSS.

As for calc I guess we had a consensus from the very beginning - so it's basically only waits for a volunteer to implement it (I estimate the quick fix to be done in just 5-6 lines of new code - so it's really only about a brave man to do this - minor implementation details may vary but I think they are fine to be decided in process).

Good! Should we track it separately to bump visibility?

@seven-phases-max:

Sure, you barely find such code in real projects (except maybe the ie \9-like hacks)

Right. Oh that bumptious "westerners"... :)

@seven-phases-max
Answer the simple question: "How 0 + 1/2 can be possibly better than (1/2)?"

I don't see where you're going with that. (1/2) should be executed by LESS because it can't possibly be valid CSS, so it must be meant as LESS. 0 + 1/2 should become 1/2 where LESS executes the 0 + 1 part because that's the part that cannot be valid CSS. The 1/2 part could be valid, so better not touch it.

@thany
OK, now realize (1/2) works as you expect in both Less and Sass..
And 0 + 1/2 (as well as 1 * 1/2 etc.) is the part of the solution you call brilliant above and "the briliant soution" resut is 0.5.
Still no clue what's happening here?
Reread everything (starting from you first post) above once more and try to answer to yourself "What exactly I'm complaining about?".

@seven-phases-max
And 0 + 1/2 (as well as 1 * 1/2 etc.) is the part of the solution you call brilliant above and "the briliant soution" resut is 0.5.

No, the solution would be 1/2 not 0.5, since 1/2 might be valid CSS. You don't seem to want LESS to know when a slash is valid, so assume it always might be. Therefor 1/2 is the only logical outcome. In the same way, 2 * 1/2 would result in 2/2, because the * would otherwise make it invalid CSS.

Still no clue what's happening here?

I'm perfectly clear what's happening here. LESS is executing math divisions way eagerly and blindly ignores units.

Reread everything (starting from you first post) above once more and try to answer to yourself "What exactly I'm complaining about?".

There's no need for personal attacks.

LESS is executing math divisions way eagerly and blindly ignores units.

So this is what you're complaining about, right?

There's no need for personal attacks.

So what should I do instead? Repeating the original two posts again and again (and again)? Until it comes clear to you:
@community:

use -sm option

@thany:

then it should be on by default.

@seven-phases-max:

It can't be because this would immediately break zillion projects out there. So if you treat the default behaviour as an issue just set the documented option and be done.


So you're repeating "it should", "it should", "it should" expecting what? Guess for us it's easier to answer just "No, it should not" instead of wasting our time trying to explain in details on why what you suggest is not going to work or makes no sense in general (the explanations you either do not want to understand or simply can't).


So what this thread is about? It's about "realizing that while an eventual breaking change of making an "-sm-like behaviour default is unavoidable, we have to provide facilities for more comfortable arithmetic other than the dreadful: margin: (1/2) (3/4) (5/6) (7/8); for those who use the arithm a lot".
You posts here either suggest something that is no way better than (or doing exactly the same as) already existing -sm behaviour or argue with something that is out of question from the very beginning (like there). In other words, just a random noise.

@seven-phases-max @thany Let's take the temperature down a notch. There are strong opinions all around.

I think most people are on the same page around both of these ideally not being interpreted by Less as a math expression:

font: 12px/10px;
width: calc(100% - 20px);

So, there's mostly agreement on those points, and most of the arguments are around possible solutions. The calc() problem looks like it has enough consensus to move forward. Basically: don't touch calc, and treat vars in calc() like string interpolation. That's pretty much how Sass does it, although it requires the interpolation syntax in calc(), which I don't think we need.

The solutions to the font part gets a lot hairier and the leading solutions have been:

  1. Require parens for everything by default (first attempt - almost implemented, but community rejected)
  2. Flip the strictMath switch (which was added instead of making it the default, and @seven-phases-max's suggestion) and then explicitly do all your math. It is, technically, a possible solution for most people at present, but....the question comes up so frequently and we know, psychologically, that someone new to any system likely keeps any defaults, so it's problematic. And not every developer can change their build settings, especially in teams.
  3. Essentially change the division operator in Less. Leading contender in the thread has been ./, which works but is a little strange to look at. \ is an unknown at this point without research. Could work, could cause unknown conflicts with escaping. It's cleaner syntax, at (potentially, but unknown) higher risk. If someone would take the time to demo that this will NOT cause conflict i.e. providing examples of escaping and math inter-mingled in a way the parser can clearly distinguish the two, then it's still a possibility. So it just takes the work. @thany, if you or someone else is a fan of this, then this is the work required. The last thing we want is just to move the breaks somewhere else.

I think, to simplify this thread, we should, from here, focus JUST on #3. I posted the Sass example mostly as a curiosity, but I do not believe those solutions are good or completely solve the problem, and they are conceptually incompatible with Less. Arguing about the validity of requiring 0 + 1/2 is not going to get us anywhere.

So, with a good solution on the table for calc(), which gets us 50% of the way there for most posted issues, I would recommend only focusing on this question in the short term:

If the Less division operator should be changed, what should it be changed to?

  • ./ - is this as awkward as I feel it is, or do people think it's fine?
  • \ - without research and proofs, and pseudo-code for a parser to follow, this won't move forward. I would personally prefer it, if it can be proven safe.
  • Other replacements?

Honestly, if we change calc() and change /, I think we would quiet 99% of the noise around math in Less.

Hey all, I fixed calc() - https://github.com/less/less.js/pull/3162

I was kinda astonished when I came up with a solution that worked without much add-on code. Basically, we already had code in for the strictMath switch, and checks in expressions to not evaluate math if it was outside parens, so I just added an additional switch on the call to turn math off while evaluating the calc() args, and then back on. We already had all the pieces to leave math alone, but still substitute vars. Huh.

Check out https://github.com/less/less.js/pull/3162/files#diff-a94aaffd78b1d3c5eda7a42d5be1ca0d and https://github.com/less/less.js/pull/3162/files#diff-4e696271823c96903a91fff84983bab6

Are the tests sufficient?

@matthew-dean :coffee:

Are the tests sufficient?

per comments in the PR add please a test like:

foo: 1 + 2 calc(3 + 4) 5 + 6; // expected result: 3 calc(3 + 4) 11;

And a minor remark: this fix obviously introduces the same issue as in #1627, i.e.

@a: floor(1.1);
@b: floor(1 + .1);

div {
    foo: calc(@a + 20%); // ok
    bar: calc(@b + 20%); // error: invalid floor arguments
    baz: @b;             // ok
}

But for calc it is fine I guess (after all there's simply no other simple ways to fix it w/o major redoing of the lazy-eval concept). So I think it's just a matter of putting a notice into the docs to keep lazy-eval in mind when using vars within calc.

And a minor remark: this fix obviously introduces the same issue as in #1627

Good catch!

Switching the context mathOn property per call solves this, similar to your comment in the PR. I've added a test for float(1 + .1) which passes!

Switching the context mathOn property per call solves this, similar to your comment in the PR. I've >added a test for float(1 + .1) which passes!

:) No, the "floor" error must be there when -sm: off. See my newer comment in the PR.
The restoring only takes care of 1 + 2 calc(3 + 4) 5 + 6-like things.

:) No, the "floor" error must be there when -sm: off.

Why? Any functions inside a calc() should be Less functions. Even if they're not, there are no CSS-native functions inside calc() that I'm aware of that can take raw math expressions. The expected result would be to evaluate vars and Less functions, but leave raw functions inside calc() alone.

Side note: I did a branch experiment of switching / to \ as a division operator in Less. There are already lots of tests for escaping (and I added more, to address: #3160), and they all still work fine, and math works fine with the operators switched. I didn't see any inherent conflict. Branch is here on my fork: https://github.com/matthew-dean/less.js/commit/509d34fff7e234846afa150b099cd259755a39d0

Re: nested functions

For this:

div {
    bar: calc(floor(1 + .1) + 20%);
}

As a developer, the expected result should be:

div {
    bar: calc(1 + 20%);
}

That's exactly what happens now (with these changes). It should not throw an error.

@matthew-dean
No, the way you wrote it, this:

foo: unit(1/2, px);

with -sm:on will compile to:

foo: .5px;

^- wrong.
Same for nested functions. Moreover, the fact that most of functions usually can't take arithmetic expressions as argument is not a reason to violate -sm: on;
Thus under -sm: on both lines:

foo: floor(1 + .1);
bar: calc(floor(1 + .1) + 20%);`

must throw a error (and this is what gets broken in the commit).

What are you talking about?

unit(1/2, px) with -sm:on:

ERROR: error evaluating function `unit`: the first argument to unit must be a number. Have you forgotten parenthesis?

calc(floor(1 + .1) + 20%) with -sm:on

ERROR: error evaluating function `floor`: argument must be a number

Check out the branch. Try it out.

One thing that might be more helpful with reviewing code is that if you aren't sure if something will produce incorrect output, please verify it. Or ask me to try a specific input to verify it works as expected. Or ask questions if you're not sure how / why it works. Calling it wrong without knowing if it is is not helpful.

My apologizes. I guess I missed the indirect control of a this part.

Check out the branch. Try it out

I can't, sorry.

My apologizes. I guess I missed the indirect control of a this part.

No worries. With that addressed, does it look like it's working as you would expect?

With that addressed, does it look like it's working as you would expect?

Yes, so far I can't imagine any other suspicious things there.

@matthew-dean
Essentially change the division operator in Less. Leading contender in the thread has been ./, which works but is a little strange to look at. \ is an unknown at this point without research [...] @thany, if you or someone else is a fan of this, then this is the work required. The last thing we want is just to move the breaks somewhere else.

Not at all actually. I suggested the \ operator as a last resort after not getting through, trying to get LESS to do only its own maths. If a new division operator is what it takes... Well, calc() can also do addition, subtraction, and multiplication. New operators for those? I think it's impractical. A new division operator might be a solution for things like font, background, and border-radius, but it's no solution for CSS maths.

I still think the real solution is for LESS to know about context. It should (yes, here I go again with my "should", what other word must I use?...) know that calc() is a CSS function and it should only evaluate maths that CSS can't do. But floor() doesn't exist in CSS, so that one it would have to evaluate (entirely) as to not spew out invalid CSS. I think this is mentioned before though, in different wording.

I still think the real solution is for LESS to know about context. It should (yes, here I go again with my "should", what other word must I use?...) know that calc() is a CSS function and it should only evaluate maths that CSS can't do. But floor() doesn't exist in CSS, so that one it would have to evaluate (entirely) as to not spew out invalid CSS. I think this is mentioned before though, in different wording.

Fair enough re: \, but as far as context, based on your other posts, I can guarantee it's a far more complex problem than you're thinking that it is. You really have to boil it down to:

12px/10px  // Is this division, or not?
10px/1.5 // is this division, or not? 

If you want to definitely leave / as a division operator, to mimic calc(), and not have it be ambiguous, then you absolutely need to make sure that the / is in parentheses (other than the calc() exception). That would provide predictable context.

That suggestion at the beginning of this thread is also still a possibility, and had some strong support. Essentially, that the default setting be that all math is supported outside parentheses EXCEPT division (and again, except calc(), is is now the case from 3.0+). Maybe that is the way to go. That would allow:

font: 10px/1.5;   // not division
font: (10px/10);  // division, result is 1px
font: 10px+15px/1.5;   // addition but not division, result is 25px/1.5
font: (10px+15px/1.5);  // result is 20px

Thing is, the result for 10px+15px/1.5 might still be hard for devs to grok, with an expression that seems to get "half-evaluated" unless it's in parentheses. If we had gone ahead and turned strict math on by default, it probably would have been fine. It's even less ambiguous. [shrug] Either way, wrapping math in some kind of context IS the way to eliminate ambiguity, and the only way for division other than changing the division operator.

The community has to essentially decide the direction. There are viable options in this thread. Each has downsides. Each has pain points. None will have full consensus. But IMO any of them are better than the current scenario. Just gotta swallow that pill.

Last call for a decision

So in an effort to work the impossible, I'd like to propose we do this (referring back to comments from 3 years ago.)

Give --strict-math 3 settings

  1. off
  2. division
  3. strict (alias on for back-compat)

To settle the debate, allow the --strict-math=division switch to do the following:

  1. Do not perform division with just / character outside of parentheses. E.g. border-radius: 55px / 25px; --> no-math (yes, that's valid CSS)
  2. Allow division with two different methods:
    a. . prefix - border-radius: 55px ./ 25px;
    b. parentheses - border-radius: (55px / 25px);
  3. Both forms are valid in parentheses - e.g. border-radius: (55px ./ 25px); would be valid

So, as a developer, if you feel one version is not your preference, you can use the other. Some may prefer not to use parentheses; some may prefer not to use a modified division operator. Something for everyone. And no more unpleasant surprises for those new to Less using what is now a widespread syntax in CSS, with the / being used in font, background, border-radius, @media queries, CSS Grid properties, and probably more in the future.

Next steps

I recommend this goes into a PR as an option, and then, as discussed, gets switched as the default for a future major version

@matthew-dean

I.e. the period acts sort of like the inverse of an escape sequence?
Not a bad idea, really...

And all things considered, _probably_ one of the cleanest possible solutions.

@rjgotten Got a start here: https://github.com/matthew-dean/less.js/tree/strict-math-division

I'm still getting a weird error one of the tests, where it it seems to turn one of the dimension nodes into an operation node (and then throws an error because it can't perform an operation on an operation node. It doesn't seem to be a parsing problem because another test not running under strictMath: division works fine. Want to check it out and see if you can help?

What I'd like to do is close this issue and create new issues that handle the remaining math questions. Specifically:

  1. Handling division, and the strictMath: 'division' feature.
  2. Handling math of mixed units. See: https://github.com/less/less.js/issues/3047
Was this page helpful?
0 / 5 - 0 ratings