Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variables are actually variables?! #297

Closed
alixaxel opened this issue Jun 28, 2011 · 40 comments
Closed

Variables are actually variables?! #297

alixaxel opened this issue Jun 28, 2011 · 40 comments

Comments

@alixaxel
Copy link

The LESS website says:

Note that variables in LESS are actually ‘constants’ in that they can only be defined once.

However, the following:

@var: red;
@var: blue;

#footer {
  color: @var; // red  
}

Generates:

#footer {
  color:blue;
}

Personally, I prefer variables to be variables, but I just can't get past the feeling that I might be missing something.

Are the docs wrong or outdated?

@crazy2be
Copy link

crazy2be commented Jul 4, 2011

What if you do

@var: red;

#footer {
    color: @var;
}

@var: blue;

#header {
    color: @var;
}

What is the output? I was wondering about this little snippet from the docs as well...

@alixaxel
Copy link
Author

alixaxel commented Jul 4, 2011

/* downloaded from http://www.below.ch/lesso/parse/a7be1745/1 */

#footer { color:red; }
#header { color:blue; }

@zachstronaut
Copy link

bump +1, I'm looking for clarification on this, too.

I feel that it is important to be able to overwrite variables. Just as a simple example... if you have a set of Less documents defining the styles for a web app, and you import a skin.less that defines a bunch of color variables as part of that, I would want to be able to reskin the app by simply including an additional myskin.less that overwrites the particular Less variables that I need to overwrite for my skin.

I don't want to have to edit the original skin.less. I don't want to have to create a new master Less document with custom import rules that imports my skin instead of the original. I don't want to have to copy and paste all of the variables from skin.less into myskin.less if I'm not going to be changing all of them.

I guess the behavior I want and expect would be that if I have:

@color: red;
... many lines of Less ...
@color: blue;

...then throughout all of the lines of Less, @color is effectively always blue.

It seems like Less actually behaves exactly like I am describing (at least in 1.2.0) ... so perhaps this is just an issue that can be solved by making things more clear in the documentation.

@n40i
Copy link

n40i commented Mar 23, 2012

I'm also looking for clarification on this issue - my test using v1.3 results in variables being overridden, which I like.

As zachstronaut comments, it's useful to override variables from thrid party sources (like Bootstrap). I intend to exploit this bug/feature for now and hope that nothing changes in future iterations of less.js.

@matthew-dean
Copy link
Member

You're misunderstanding, I believe, and yes, I think that description comes from earlier inceptions of LESS.

The reason they are not EXACTLY variable is that, outside of mixin parameters, variables have a set value in their context. Everything is evaluated sequentially, and values are essentially replaced.

In other words, it's not like I don't know what @var will be, based on execution. @var will ALWAYS be exactly what it is, a value that has been assigned (a constant) in the process of evaluation.

I don't know if that explanation is very good, but that's always been my interpretation of that statement. Variables NEVER change their value, because they always have a completely set code path. That does not mean they cannot be reassigned, but they are reassigned another constant value. (Which, by some definitions, makes them variable, but now we're getting into semantics.)

@lukeapage
Copy link
Member

the first declaration in the current scope is used rather than the last.. so to override you put override first. some projects even rely on this..

dotless uses variables mode (last declaration) and a flag to switch between less and dotless modes.

@bthule
Copy link

bthule commented Aug 11, 2012

Was this code changed at some point? In Less 1.3.0 the last variable declaration in the current scope is used... so to override you put override last.

So the original poster's output CSS in Less 1.3.0 is:

#footer { color:blue; }
#header { color:blue; }

CSS does this the same way. For example, the css below would make web browsers use the color:blue for the .test selector.

.test { color:red; }
.test { color:blue; }

I think this is by design, and good. But, the terrible thing is that less.cs doesn't seem to provide any good control on scoping per file. So if I want to make one nice big .css file, and import bootstrap into it, then I have to be careful not to use a variable that bootstrap uses.

@bthule
Copy link

bthule commented Aug 11, 2012

By the way, the lessphp (a less compiler written in PHP) appears to generate CSS differently than less.js. The reason alixaxel's output was different than less.js is because the http://www.below.ch/lesso/parse/a7be1745/1 link he provided is using lessphp. I am guessing dotless works similarly to lessphp. I couldn't find an online dotless css generator to test.

The dotless team thinks the way that less.js works might be a bug. It seems that it may, but I wonder what the dotless stance is on allowing mixins to contribute variables to the scope they are called. I personally have found it to be an annoyance because of the unintended consequences of bringing in someone else's less code as an import, however I wonder if has some usefulness for setting variables in a sub-scope (I couldn't think of any examples).

@agatronic, did you ever hear from @cloudhead on whether he thinks this is a bug? The lesscss.org page says "Note that variables in LESS are actually ‘constants’ in that they can only be defined once.", but that seems like it would be even worse than the way less.js actually works now.

My vote is to change less.js to work like dotless and lessphp

@lukeapage
Copy link
Member

dotless does not leak variables from a called mixin into the scope it is called from.

I have not seen any change in the way less.js gets the variable to use, but testing it now, you seem to be right,

@nathanielks
Copy link

I opened #905, and I agree with @bthule in this comment, that Less should behave more like dotless and lessphp.

@lukeapage
Copy link
Member

@MatthewDL @Synchro - being part of dotless I'm all for this.. what do you think, should we go ahead? my testing seems to show 1.3.0 gained this behaviour and no-one has complained so I think it would be a sane place to take less.js. WDYT?

@Synchro
Copy link
Member

Synchro commented Oct 24, 2012

I think it's less confusing for it to act as suggested (last declatarion) - and I've often found that when I expected it to act like the docs say, it acted like a variable anyway!

@nathanielks
Copy link

The problem with that though is that they're not actually acting as VARIABLES. Why would they be called variables if they behave as constants?

@matthew-dean
Copy link
Member

I don't actually understand the problem. In the first two examples, it behaves exactly as I would expect, in that it follows the way properties are declared in CSS, which is what LESS is mimicking. It may be documented in a confusing way, but if I declare:

@var: blue;
@var: red;

The value of @var should be red.

Just like this DIRECTLY from the documentation:

@var: red;

#page {
  @var: white;
  #header {
    color: @var; // white
  }
}

#footer {
  color: @var; // red
}

Within the #page block, the documentation states that the color should be white.

So, I don't understand the issue. The examples seem to be working as designed.

@matthew-dean
Copy link
Member

Further to the point: this line in the documentation, "Note that variables in LESS are actually ‘constants’ in that they can only be defined once." is demonstrably untrue, as further along in the documentation, those variables are demonstrated to change their value within a mixin / block scope. (See "Scope")

So, if that line were removed, it would help clear up confusion. Variables are variables, and you can and should be able to redefine them as much as you want, just as you would with any CSS property. Any case outside of THAT is a bug, but I see no bug here.

@nathanielks
Copy link

The problem arises when you want the variable to be different values in different parts of your document. This comes into play especially when using grids.

Lets say I want @column-width to be 60px initially. Then later in the document in a @media query I reset @column-width to be 40px. When Less compiles it, it sets @columns to be 40px across the entire document, as opposed to changing it as it traverses the document. Illustration:

@column-width: 60px;

.column{
   width: @column-width;
}

@column-width: 40px;

.column{
   width: @column-width;
}

It'll output 40px for both instead of 60px and 40px, respectively.

@matthew-dean
Copy link
Member

Right. You can change it per scope, which is probably how it should be re-written. Just like multiple declarations of, say, a width property within a block results in the final one declared being the final value.

As an alternative, you could define mixin blocks with a local value, or pass those column width values to the mixin to get the result you want.

It certainly seems unclear in the documentation. But this still appears to work as designed.

@nathanielks
Copy link

Yup, exactly. And I'd agree it works as designed. But I would like it to work this way ;)

@lukeapage
Copy link
Member

@MatthewDL.. because..

  1. It didn't used to
  2. If we assume it should act like variables, there are a few scoping
    issues.

But yes I think, fix the scoping issues to be how you might expect and
rephrase the docs.. imo

@matthew-dean
Copy link
Member

If it worked differently in the past, I think it might have been because of other scoping issues that @cloudhead addressed, but I couldn't say for sure offhand.

Yes, there may still be scoping issues. I'm just pointing out how it appears to be designed, so that we can accurately frame what is and isn't a "bug".

@lukeapage
Copy link
Member

Another good example..

@var: red;

#footer {
  color: @var; 
}
@var: blue;

outputs...

#footer {
    color: blue;
}

@matthew-dean
Copy link
Member

Yes, that's the correct output. LESS is NOT a programming language.
Its not a script that runs sequentially. Its a declarative language.
Variable values are evaluated per block, with the final one overriding
previous values.

@Soviut
Copy link

Soviut commented Oct 25, 2012

You shouldn't be reusing variable names in the same scope.

@lukeapage
Copy link
Member

@MatthewDL I well understand what DECLARATIVE means. It doesn't stop it being confusing.. Its easy to take a highly simplified view of things but do you not see how you are contradicting yourself "Variable values are evaluated per block, with the final one overriding previous values." - that implies sequential behaviour.. there are LOTs of inconsistencies in the way less handles variables and scope.

Maybe it doesn't matter too much - the usecase for overriding variables is mostly themes and getting default values for theme variables that are not overridden and less does this at the moment.

@SomMeri
Copy link
Member

SomMeri commented Oct 26, 2012

If the variables stay constants, what about outputting a warning to the console in case of overriding the variable within the scope?

@matthew-dean
Copy link
Member

I don't disagree that it's potentially confusing for programmers who are
thinking in terms of sequential script execution. Just saying its
consistent with CSS behaviour. LESS is imitating CSS's cascading behaviour.
Instead of it throwing an error at duplicate variable declarations, it's
adopting the last value. Having said that, since LESS is more strict than
CSS, we should just throw am error when this happens. That way, the output
isn't unexpected. People don't see an error, so are then thinking its
evaluating sequentially.

@matthew-dean
Copy link
Member

Throwing an error would also keep behavior in line with the documentation.
Variables can be defined or overridden once per block scope.

@Soviut
Copy link

Soviut commented Oct 26, 2012

He said it isn't a sequential scripting language, meaning you can't do:

@primary: red;
background: @primary;
@primary: blue;
foreground: @primary;

LESS essentially flattens all the variables in a scope and uses the final result.

@matthew-dean
Copy link
Member

@Soviut Correct. And since blocks inside inherit those other variables, you would get the same result even if background and foreground were wrapped in curly braces.

@matthew-dean
Copy link
Member

For example, here's a "LESS" way of solving @nathanielks example:


.setColumn(@column-width) {
  .column{
     width: @column-width;
  }
}

.setColumn(60px);
.setColumn(40px);

You can have the same class outputting from the same variable, with two different values set in sequence. You just can't redefine the variable in that scope and have all subsequent code use the new value, and prior code use the old value. I think that's what @cloudhead intended to mean by the word "constants".

@lukeapage
Copy link
Member

ok, fine, I think your mis-interpretting me. I think the current behaviour is fine and I should have thought about that example a little more - it can be confusing, but is the expected behaviour.

throwing an error when you define a variable for a second time would be a big step backwards - as I said - most of the use-cases for multiple definitions come from theming and having default variables which you want to keep in the less to aid development and have the variables close to the rulesets/mixins. The only way to do that is to define a variable and then over-write it later. It used to be people had to put the overrides first, now they have to put the overrides last.. thats fine and it makes sense. Throwing an error would solve no problems and just make using less in this context more difficult/impossible.

So lets close this bug and leave things as they are.. There are other bugs that cover scoping issues and more complex questions. ??

@Soviut
Copy link

Soviut commented Oct 28, 2012

@MatthewDL The best way to think about is the way a real CSS file works. If you define the color attribute twice, it will use the last one:

body {
  color: red;
  color: blue;
}

This is because CSS is declarative. There is no concept of "when" something was declared since there are no procedural "frames" or program flow. Your declarations will overwrite each-other in the same scope in CSS; The same is true for LESS.

@matthew-dean
Copy link
Member

Correct. We've determined it works as designed (at least this particular behavior), so should be closed. @agatronic Wasn't intending to offend, just clarify for others.

@cweekly
Copy link

cweekly commented Nov 9, 2012

I'm glad I'm not the only one who's been confused about legality of redefining variables in LESS. Yes, the docs say they're constants... yet (at least as of less 1.3.0), redefining a given var multiple times works fine.

For the record, there is now even explicit testing for this behavior in 1.3.0.
So I think at this point the ambiguity of the word "constant" in the documentation is the only meaningful problem.

It seems the lessc compiler does its parsing in one pass, and for any variable defined more than once, the last definition wins, so the last-defined value is used for all references to that variable. That final value is then "constant". But redefining variables in .less (as in the common case of theming, or overriding a vendor-provided set of less files a la twitter-bootstrap) doesn't seem to pose any problems.

Note the test for this in the main project's test/less/variables.less:

.redefinition {
  @var: 4;
  @var: 2;
  @var: 3;
  three: @var;
}

which compiles to

.redefinition {
  three: 3;
}

... so it works, AND it is explicitly tested for in the official project's test suite. It seems like the only reason to consider variable redefinition illegal is the ambiguity in the docs' wording. Would LOVE confirmation from @cloudhead though!

@Soviut
Copy link

Soviut commented Nov 9, 2012

I think the term constant is generally correct in that they only ever have one value. However, like anything in CSS, they can be cascaded. I can't think of a better term for it than that.

@matthew-dean
Copy link
Member

Right. Maybe the docs could simply say they have a value that can be overridden in that scope, like CSS? The "root-level" scope might need to be explained, since it's not wrapped in curly braces.

@lukeapage
Copy link
Member

I created less/old-lesscss.org#43 to cover this.. I think @MatthewDL's css analogy is a good way to explain it.

@Soviut
Copy link

Soviut commented Nov 10, 2012

Yeah, basically you could claim that values are like "CSS attributes", their values are constant, global to their scope, but can be overridden by cascading.

strk pushed a commit to CartoDB/carto that referenced this issue Sep 11, 2013
strk pushed a commit to CartoDB/carto that referenced this issue Sep 11, 2013
strk pushed a commit to CartoDB/carto that referenced this issue Oct 28, 2013
strk pushed a commit to CartoDB/carto that referenced this issue Oct 28, 2013
@gacekssj4
Copy link

Adding @var at end of documents yields results:

// Equivalent to --modify-vars option.
// Properties under options.modifyVars are appended as less variables
// to override global variables.
var modifyVarsOutput = parseVariableOptions(options.modifyVars);
if (modifyVarsOutput) {
  srcCode += '\n' + modifyVarsOutput;
}

https://github.com/gruntjs/grunt-contrib-less/blob/master/tasks/less.js @ line ~144
And it works.

@coresh
Copy link

coresh commented Mar 24, 2017

Thanks, @matthew-dean

Also allowed:

.setColumn(@column-width, @column-color) {
    .column{
        width: @column-width;
        color: @column-color;
    }
}

.setColumn(60px, #ff0000);
.setColumn(40px, #ffffff);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests