Skip to content

dot notation for variables #1357

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

Closed
jonschlinkert opened this issue Jun 6, 2013 · 26 comments
Closed

dot notation for variables #1357

jonschlinkert opened this issue Jun 6, 2013 · 26 comments

Comments

@jonschlinkert
Copy link
Contributor

Just throwing this out because I thought it was an interesting idea, and it could be a nice addition to the language for organizing code.

The concept is that variables could be defined in a similar way to JavaScript objects, so given this:

@palette: {
    primary: #0000E0;
    info: #02d7e1;
    success: #02e10c;
}

You could then apply the variables like this:

.btn {
  background: @palette.info;
}

I suppose there are no transformative advantages to doing this, but it could be useful as a way of organizing "groups" of variables.

@jonschlinkert
Copy link
Contributor Author

It would be even more interesting if the "base" variable could be interpolated.

@palette: something;

@{palette}: {
    primary: #0000E0;
    info: #02d7e1;
    success: #02e10c;
}
.btn {
  background: @{palette}.info;
}

@matthew-dean
Copy link
Member

First, I love this.

As an idea, why not just use the same pattern as how selectors are defined? (Drop the colon and follow with the block.)

@palette {
    primary: #0000E0;
    info: #02d7e1;
    success: #02e10c;
}

You could then also do something like the LESS pattern for namespacing mixins, which follows the CSS pattern for child selectors:

.btn {
  background: @palette > info;
}

I think also that the interpolation might be different. If you're wishing to dynamically aim it at another variable, with the @@something or @@palette syntax, otherwise @{palette} would convert to this

something {
    primary: #0000E0;
    info: #02d7e1;
    success: #02e10c;
}

Which doesn't quite make sense.

@matthew-dean
Copy link
Member

So, to be clear, this might be your final syntax, with interpolation:

@palette: something;

@something {
    primary: #0000E0;
    info: #02d7e1;
    success: #02e10c;
}
.btn {
  background: @@palette > info;
}

Palette is substituted with a reference to @something. I believe that's how @@ works, although I haven't used it personally.

@jonschlinkert
Copy link
Contributor Author

why not just use the same pattern as how selectors are defined?

I thought about that and that's initially what I had when I started writing it, but I put the colon there since variables are already defined with a colon, and specifically because selectors are defined without them. It just seems more axiomatic to the Less.js syntax. However, the proposed spec for CSS custom properties ("CSS variables"), does have a syntax like this:

:root {
  var-foo: calc(var(one) + 20px);
  var-bar: calc(var(two) - 20px);
}

(from memory, I think that's pretty close to an example in their spec to give credit where it's due - unless it's wrong then it's mine ;-)

I'm not convinced of whether that supports going with the colon or without it, I just wanted to mention it. (and this has no bearing, but for the record I'm not a fan of that proposal).

Your point about @@ makes sense, but if I'm not mistaken (since I don't use that often either) I think it would be @{@palette}.

To your last suggestion, I still prefer the dot notation, IMO it's more obvious that info "belongs" with palette like this: @palette.info, than like this @palette > info. I see where you're coming from though, but variables aren't selectors so I'm not sure if it's beneficial to imply that relationship.

@matthew-dean
Copy link
Member

Variables have a colon normally because it's a key / value pair. CSS follows that too. When blocks are defined, they typically just have a keyword and then the following block. Again, I would refer to something like @Keyframes definition or normal selector blocks. I get where you're coming from; however, I don't think you would ever see a pattern like keyword : { } in CSS, but it does have the concept of defining property value pairs that way (@Keyframes).

On @palette > info, I'm not attached to that since I consider the #namespace > .mixin() syntax kind of clunky anyway. You're right, that one is just an arbitrary metaphor, since it's not REALLY a child selector in the same way. That was just an effort to be consistent. I wouldn't necessarily be against @palette.info... except the dot notation for properties is kind of a JavaScript concept, and in CSS, the dot is already reserved for classes.

So, I agree that @palette.info is cleaner than @palette > info, but it does seem to somewhat clash with class selector (and mixin) syntax.

Other variants that are CSS-style?

@palette[info];
@palette{info};
@palette (info);

? Dunno.

@lukeapage
Copy link
Member

see #76 and #6 :)

@jonschlinkert
Copy link
Contributor Author

Thanks for pointing those out, but I think those requests are from individuals who are far more intelligent than myself. This is just a request for "syntactic sugar" for variables. Not (technically) for accessors or accessing properties on rule-sets. Since there would be no change in the way variables are used or created, nor in how they are scoped.

(Before I go on, @lukeapage I'm not attached to this, I just thought it was a neat idea. We have more important issues to tackle so you should feel comfortable closing this if you don't think it makes sense for Less.)

In #76, "Accessing specific properties from rule-sets", the OP was asking for the ability to access the value of any property in any given rule-set, which is not what I'm proposing. There might be dialog at some point that turns to something like what I'm proposing, but admittedly I skimmed that one.

And #6 is a request for accessors, which is also not what I'm proposing and it's more similar in spirit to the #76. IMO both requests stayed pretty focused on using selectors or mixins for namespacing variables. That is possibly part of the reason they were stuck discussing clunky syntaxes.

// #color[@brand]
#color {
    @brand: #DDD;
    @background: #EEE;
    @title: #555;
}

But I guess in spirit both requests are asking for "namespaced" variables, which is related to what I'm proposing. And both requests had a lot of +1s so the concept was popular. Just my 2c

@matthew-dean
Copy link
Member

@jonschlinkert Looking at that second example, you COULD namespace using mixins right now. Calling a mixin that has variables imports that into the current scope, which could become the global scope.

So you could do:

.colorset1() {
    @brand: #DDD;
    @background: #EEE;
    @title: #555;
}
.colorset2() {
    @brand: #FFF;
    @background: #DDD;
    @title: #666;
}
.colorset2();
.box {
  color: @brand;
}

So, does that work if it allows you to do the same thing in a different syntax?

@lukeapage
Copy link
Member

@matthew-dean we decided that was a bug which has massive nasty consequences in more complicated cases.

@lukeapage
Copy link
Member

@{palette}: { primary: #0000E0; info: #02d7e1; success: #02e10c; }

thats the same as selector interpolation, so we couldn't tell if it was a ruleset or a variable definiton

@jonschlinkert
Copy link
Contributor Author

thats the same as selector interpolation, so we couldn't tell if it was a ruleset or a variable definiton

With the colon even?

@matthew-dean
Copy link
Member

The colon could be the start of a pseudo selector.

we decided that was a bug which has massive nasty consequences

Oops. I've been relying on this and actually coding stuff because of this "supported" behavior, lol. Which means I may not be the only one. (Something to consider.) Why is it a bug? Aren't I dumping the contents of that mixin at that location, including variable declarations?

@jonschlinkert
Copy link
Contributor Author

I don't know, the "mixin" syntax proposed by @matthew-dean is not at all what I wanted to accomplish with this request. It's a "nice-to-have", but the request was really centered on using dot notation for variables, since that syntax has such obvious denotations in programming, and using the "object" syntax to wrap "groups" of variables, since that syntax also has an established relationship with dot notation.

IMO the most important point made was @lukeapage's, regarding how @{class} is already used for selector interpolation. Maybe we should do something different (assuming anyone besides me is actually interested in this feature. if not we can just close it).

For inspiration, CSS already has at-rules with the following syntaxes (I know there are more, but none are coming to mind right away):

@rule "";
@rule {}
@rule () {}
@rule url();
@rule url() screen and (key: value);
@rule:pseudo-class {}
@rule( (key: value) or (key: value) or (key: value) or (key: value) or (key: value) ) { // any nested at-rules }
...

I'm assuming interpolation would be a challenge with any of these? Maybe we should think about using something like :variables {}, similar to :root {} in CSS? Since :root is the example I see most often associated with CSS variables (proposal for CSS custom properties).

@lukeapage
Copy link
Member

@matthew-dean

we decided that was a bug which has massive nasty consequences

Oops. I've been relying on this and actually coding stuff because of this "supported" behavior, lol. Which means I may not be the only one. (Something to consider.) Why is it a bug? Aren't I dumping the contents of that mixin at that location, including variable declarations?

I forgot that we(or me?) then decided it was too much of a breaking change and kept the existing behaviour

@lukeapage
Copy link
Member

@jonschlinkert is it not achieving the same thing, with a simpler syntax to do this..

#scope {
    @myvar: test;
}

@access: #scope > @myvar;

it then matches the scoping you can do for mixins and it doesn't complicate the parsing (as much)

@jonschlinkert
Copy link
Contributor Author

@lukeapage yeah I'm sold on that, that's better than my suggestion I think.

@Soviut
Copy link

Soviut commented Jun 16, 2013

+1 for allowing variables in existing namespace syntax. I've got a grid semantic system that I'd really like to namespace but can't because I won't be able to properly expose any of the settings or constants.

@extemporalgenome
Copy link
Contributor

I have a need for something like this, and was going to scan the issue list and potentially start a proposal, but realized that none of the several options I considered were anything but inconsistent and problematic; some of those options are listed here.

First, the @x: #y > z style syntax is troublesome for a number of reasons:

  1. How do we distinguish between selectors intended as value-getters, and selectors intended to be literal values? There are already use cases for stuffing literal selectors in variables, including for use with extends, and the magic behavior of using selectors as value getters seems like it should be the thing wrapped in a @{}, rather than needing to ~"" literal selectors.
  2. Multiple mixins or blocks can match the selector. Which then is used (should it follow CSS conventions for specificity and then source order)? Should an error result if more than one applies, just as if none applied? The effect of this on variables could result in rather hard to investigate (or even notice) bugs, and if implemented, an annotation output mode should also be added to lessc that inserts comments indicating the file:line-number source of every 'selected' value.
    1. There is no rule that prevents variables from having the same names as attributes in the same scope. How then would @x: #y > z be interpreted if you want to select the value of the variable named 'z' rather than the property? It's reasonable that @x: #y > @z should look up the variable of '@z' in the local scope before attempting to resolve the selector, and @x: #y > ~"@z" is non-obvious.

Regarding the original @palette.info proposal, since LESS is a CSS preprocessor, any extended behavior really should follow CSS conventions. For example:

@widget: {
    large: {
        padding: {
            left: 5px;
            top: 6px;
        }
    }
}
@x: @widget.left;

According to CSS semantics, since non-space separated dot prefixes indicate an unordered collection of classes, @widget.left should be valid (if that's the chosen notation), and should have the value of 5px. Structurally that doesn't make much sense though.

Whatever the definition syntax for nested variables (whether you're selecting variables out of mixins/blocks or specialized deep variables as initially suggested, it seems like the cleanest thing to do would be to introduce a new syntax. For example: @{[widget > large > padding > left]} with rigid child-only selector capabilities if 'deep variables' are favored, or @{[.some-mixin some-descendent > @var]} syntax with arbitrary selector support if variables can be accessed out of mixins or blocks, but with very clear and well-defined lookup semantics.

In any case, I feel the real usefulness of something like this would be general settings with specific overrides, e.g.:

#config() {
    @color: gray;
    @border-width: 1px;
    .widget {
        @border-width: 2px;
    }
    .menu {
        @color: black;
    }
}
@x: @{[#config > .widget > @color]};        // inherits @{[#config > @color]}, which is gray;
@y: @{[#config > .toolbar > @border-width]} // even though .toolbar is not defined, falls back to 1px from #config.
@z: @{[#config > .menu > @color]}           // yields value 'black', as explicitly defined in .menu

Of course, being able to fetch the applicable value of a given attribute would be far more useful for this kind of thing in practice, since there's no need for a @border-width variable if you can fetch the value of a given border-width attribute.

@lukeapage
Copy link
Member

First, the @x: #y > z style syntax is troublesome for a number of reasons:

How do we distinguish between selectors intended as value-getters, and selectors intended to be literal values? There are already use cases for stuffing literal selectors in variables, including for use with extends, and the magic behavior of using selectors as value getters seems like it should be the thing wrapped in a @{}, rather than needing to ~"" literal selectors.

I have commented that I don't want less to be parsing selectors directly as values.. but I would support having a special function e.g. @a: selector(#y > z)

Multiple mixins or blocks can match the selector. Which then is used (should it follow CSS conventions for specificity and then source order)? Should an error result if more than one applies, just as if none applied? The effect of this on variables could result in rather hard to investigate (or even notice) bugs, and if implemented, an annotation output mode should also be added to lessc that inserts comments indicating the file:line-number source of every 'selected' value.

Why would an error be hard to debug? I think it should error if it matches multiple.

There is no rule that prevents variables from having the same names as attributes in the same scope. How then would @x: #y > z be interpreted if you want to select the value of the variable named 'z' rather than the property? It's reasonable that @x: #y > @z should look up the variable of '@z' in the local scope before attempting to resolve the selector, and @x: #y > ~"@z" is non-obvious.

if we matched properties or variables and showed an error if we matched 2 then people would just have to rename their variables if they wanted to use this feature

Whatever the definition syntax for nested variables (whether you're selecting variables out of mixins/blocks or specialized deep variables as initially suggested, it seems like the cleanest thing to do would be to introduce a new syntax. For example: @{[widget > large > padding > left]} with rigid child-only selector capabilities if 'deep variables' are favored, or @{[.some-mixin some-descendent > @var]} syntax with arbitrary selector support if variables can be accessed out of mixins or blocks, but with very clear and well-defined lookup semantics

I agree it is probably a good idea to have a way of wrapping it.. we could then implement simple cases to begin with and have the option of expanding it to what you describe with multi-selectors etc. @{[]} seems a little extraneus though?

@extemporalgenome
Copy link
Contributor

I have commented that I don't want less to be parsing selectors directly as values.. but I would support having a special function e.g. @A: selector(#y > z)
Why would an error be hard to debug? I think it should error if it matches multiple.
if we matched properties or variables and showed an error if we matched 2 then people would just have to rename their variables if they wanted to use this feature

All very reasonable. I hadn't considered the practicality of the same syntax selecting either attrs or vars, but especially if we can select attrs too, there'd be little reason to have same-named variables in practice. I think 'select' would be a slightly better name, since 'selector', while accurate to what it's operating on, suggests it's "forming a thing", while 'select' suggests it's picking something.

I agree it is probably a good idea to have a way of wrapping it.. we could then implement simple cases to begin with and have the option of expanding it to what you describe with multi-selectors etc. @{[]} seems a little extraneus though?

That syntax suggestion wasn't meant to be taken serious in form, but rather function: it's unambiguous with current syntax rules, and would allow a way to interpolate arbitrary expressions. Admittedly, your intention for 'selector' and its syntactically identical handling of var and attrs eliminates the stated need for @{[]}, though @{[]} attempted to solve one thing that 'selector' does not:

It seems to be an unfortunate (but most likely thoroughly considered) legacy of the syntax that interpolation doesn't accept an expression, e.g. @{convert(@some-var, "px")}, but rather expects only a name, in many cases resulting in the need to create a throwaway variable (and since variables have the value of the last definition in the nearest in-defined scope, it means you cannot just reuse something like @tmp between subsequent expressions, as you might in imperative code, instead requiring one variable for each such expression). Certainly @{@some-var} would not be syntactically clean, though some other syntax might, for example $(some expression) would be parsed out of strings, and would be equivalent to (some expression) there, and anywhere else it's encountered. For example (although pointless), div { x: $($(3 - 2) * 6) } would be equivalent to div { x: ((3 - 2) * 6) }. The reason for allowing $() outside of strings would be to ease usability, such that wrapping it in or stripping quotes from around it would still result in valid LESS code, and in any case could functionally replace (and would be much more powerful than) @{}.

@skyshab
Copy link

skyshab commented Feb 18, 2014

This! I just thought up the concept myself, and after getting an error on test decided to see if anyone else had the idea.

I think Less needs to evolve in a manner that resembles JS, and this would be a great step forward! Being able to assign properties to variables would make it much easier to organize and pass arguments, switch out groups of properties based on other values, etc.

It would also be great if you could could assign a mixin as the value of a var, like:
@Myvar: .myMixin();

Also, FWIW, I prefer the dot notation to the ">", and think it looks cleaner.

@jonschlinkert
Copy link
Contributor Author

I prefer the dot notation to the ">", and think it looks cleaner.

agreed!

@matthew-dean
Copy link
Member

I think Less needs to evolve in a manner that resembles JS

Less is designed to resemble CSS. JS just happens to be the language of the Less language parser, so Less features are not, by design, JavaScript-based. Because many of us are JavaScript developers, that's sometimes where some inspiration comes, but I don't believe part of the mission is to merge the two.

Discussion should maybe be continued on #1848?

@zamtools
Copy link

The whole point of LESS and SCSS is to be inviting to web designers who already write CSS, not programmers. What you're describing is a different product.

@skyshab
Copy link

skyshab commented Feb 18, 2014

I suppose what I meant is that I'd like to see Less adopt a more object-oriented approach, not necessarily anything JS specific.

Thanks for the link to the other issue!

@lukeapage
Copy link
Member

closing as duplicate

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

7 participants