less / less.js

Less. The dynamic stylesheet language.
http://lesscss.org
Apache License 2.0
17.01k stars 3.41k forks source link

How to handle Maths #1880

Closed lukeapage closed 6 years ago

lukeapage commented 10 years ago
  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).

seven-phases-max commented 10 years ago

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...).

lukeapage commented 10 years ago

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

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

seven-phases-max commented 10 years ago

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"...

lukeapage commented 10 years ago

related: https://github.com/less/less.js/issues/1480

lukeapage commented 10 years ago

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

seven-phases-max commented 10 years ago

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.

calvinjuarez commented 10 years ago

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.

seven-phases-max commented 10 years ago

@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).

ponychicken commented 9 years ago

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

background: url(image.png) 50%/300px no-repeat;
matthew-dean commented 9 years ago

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.

matthew-dean commented 9 years ago

Our --math options could be more like:

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

seven-phases-max commented 9 years ago

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.

matthew-dean commented 9 years ago

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.

seven-phases-max commented 9 years ago

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).

matthew-dean commented 9 years ago

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.

seven-phases-max commented 9 years ago

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)...

matthew-dean commented 9 years ago

Yeah, I get you.

calvinjuarez commented 9 years ago

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.

corysimmons commented 8 years ago

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?

matthew-dean commented 8 years ago

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

matthew-dean commented 8 years ago

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?

matthew-dean commented 8 years ago

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];
seven-phases-max commented 8 years ago

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
}
matthew-dean commented 8 years ago

@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?

matthew-dean commented 8 years ago

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?)

seven-phases-max commented 8 years ago

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).

matthew-dean commented 8 years ago

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.

matthew-dean commented 8 years ago

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).

seven-phases-max commented 8 years ago

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.

matthew-dean commented 8 years ago

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.

seven-phases-max commented 8 years ago

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).

leewz commented 7 years ago

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.

seven-phases-max commented 7 years ago

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).

leewz commented 7 years ago

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?

seven-phases-max commented 7 years ago

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?

matthew-dean commented 7 years ago

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]

matthew-dean commented 7 years ago

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):

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.

seven-phases-max commented 7 years ago

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".

matthew-dean commented 7 years ago

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

stale[bot] commented 6 years ago

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.

seven-phases-max commented 6 years ago

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

rjgotten commented 6 years ago

@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...

seven-phases-max commented 6 years ago

My current vision of the proposal:

rjgotten commented 6 years ago

@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.

seven-phases-max commented 6 years ago

@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).

matthew-dean commented 6 years ago

@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.

matthew-dean commented 6 years ago

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?)

rjgotten commented 6 years ago

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.

seven-phases-max commented 6 years ago

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:

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).

rjgotten commented 6 years ago

@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.