less / less-meta

Like meta.stackexchange, but for Less. For documentation, please go to less/less-docs
MIT License
5 stars 1 forks source link

Variable / property namespacing #12

Open matthew-dean opened 8 years ago

matthew-dean commented 8 years ago

The discussion around namespacing had some proposals on syntax, but I'm starting to think https://github.com/less/less.js/issues/2767 makes the proposal problematic. https://github.com/less/less.js/issues/1848

Yes, that was part of the namespacing discussion. However, after some of our discussion around unifying mixins and detached ruleset behavior, I'm not sure the syntax really makes sense.

Namespacing

For example, we were talking about:

val: @{#ns var};
val: ${#ns prop};

...with var and prop a variable and property inside that namespace, and the @ and $ applying to the last identifier. But if we make a detached ruleset a kind of (anonymous mixin assigned to a var), then this logic begins to quickly get confusing.

val: @{@dr var};  // which one does @ belong with?

We also talked about the short form #ns@var, which becomes something like @dr1@dr2@var, which conflicts with other syntax.

So, I'm thinking for 3.0, we re-think it to one of the earlier suggestions by @lukeapage which was close to the original Ruby syntax with [] for properties:

val: #ns[@var];
val: #ns[prop];
val: @dr[@var];
val: @dr[prop];

That places the @ symbol closer to the actual variable, and makes the identifier it belongs with less ambiguous.

Interpolation

... Discussion moved to #13, as it's not necessary to do both at the same time, or at all.

matthew-dean commented 8 years ago

By the way, a way to think about property / variable accessing is basically, this:

foo: #ns[@bar];

is sugar for this:

& {
  #ns();
  foo: @bar;
}

(Unless we want to get clever with how we select which var values are valid)

seven-phases-max commented 8 years ago

There's one more important thing I guess we forgot to discuss earlier. When accessing object member we usually need both direct and indirect access, i.e. using JS-semantics:

obj["ident"]; // -> obj.ident
obj[var];   // -> obj.ident-in-var

At quick glance it's not that difficult to target this for var fields using "indirect variable referencing" syntax as the base, e.g.

@var: foo;
val: #ns[@var];  // -> ns.var-variable
val: #ns[@@var]; // -> ns.foo-variable

but then:

@var: foo;
val: #ns[@var];  // -> ns.var-variable
val: #ns[@@var]; // -> ns.foo-variable
val: #ns[prop];  // -> ns.prop-property
val: #ns[??];    // -> ns.foo-property

So we need more ideas.

matthew-dean commented 8 years ago

@seven-phases-max Are you just talking about:

@var: foo;
val: #ns[$@var];    // -> ns.foo-property

If we do @@var, then $@var seems like the logical extension of that.

Also, I guess that could include variations of $$prop and @$prop although I'm not sure the use-cases, unless it's essentially because you are using properties as vars from a namespace?

seven-phases-max commented 8 years ago

If we do @@var, then $@var seems like the logical extension of that.

Yes, but then should not we also require val: #ns[$prop] instead of #ns[prop]? Otherwise a common mistake will be

@foo: bar;
val: #ns[bar];
val: #ns[@foo]; // oops, I thought that means #ns[bar] too
seven-phases-max commented 8 years ago

Now, after thinking of it a bit more,

@foo: bar;
val: #ns[bar];
val: #ns[@foo]; // oops, I thought that means #ns[bar] too

Do we really need to be that strict there at all? Can't we make #ns[ident] to match both property and variable (withwhatever reasonable precedence)? Then:

@foo: bar;
val: #ns[bar]; // can mean both #ns.bar-property and #ns.bar-variable (whichever happens to be there)
val: #ns[@foo]; // means the same thing (and then we retain the usual `@foo` semantics).

Sure it's about "guessing a user intention, bla-bla-bla" - but can we imagine any practical snippet where you have:

#ns() {
    @foo: 2px;
    foo: @foo * 3;
}

and really want to have an access either to #ns[@foo] or #ns[$foo]?. I.e. would a supposedly minor ambiguity (and limitation of course) be worth a more clean syntax?

seven-phases-max commented 8 years ago

And yet another problem I did not want to mention before because it's also quite a story on its own. I already mentioned that currently namespaces are not "open" to each other. And this actually introduces quite dramatic problems with "overriding" in a longer run.

Let's start with this snippet:

is sugar for this:

& {
  #ns();
  foo: @bar;
}

Now let's try some basic overriding:

#ns {
  @bar: 42;
}

usage {
  #ns();
  foo: @bar;
}

#ns {
  @bar: 43;  
}

^ works like a charm.

Now something more close to practical use-cases:

#ns {
  @bar: 42;
  @baz: @bar * 2;
}

usage {
  #ns();
  foo: @baz;
}

#ns {
  @bar: 43;  
}

Fail... :( (foo is 84 and not 86 as we would expect it to be for proper overriding purposes). The result is somewhat correct in fact, since when each #ns mixin is evaluated, its local variables have higher priority (or in other words, two #ns mixins are evaluated independently, thus while the second do override @bar within the usage scope, it does not override it in the first #ns scope).

That's something we'll have to target too. (Note that we cannot change mixin evaluation behaviour in this case - reason).

matthew-dean commented 8 years ago

Yes, but then should not we also require val: #ns[$prop] instead of #ns[prop]? Otherwise a common mistake will be

@foo: bar;
val: #ns[bar];
val: #ns[@foo]; // oops, I thought that means #ns[bar] too

I don't see how that would be "common". In [] we're specifying what's being returned, so I probably should have said that #ns[@@foo]] is illegal as well. There's no such variable name as @@foo, which means we can probably ditch @$ $@ combinations, because the use case needs begin to drop steadily.

Can't we make #ns[ident] to match both property and variable (withwhatever reasonable precedence)?

At first I didn't know what you meant until I read it. And then it makes sense and yes, does increase the simplicity of the syntax.

#ns[ident]  // either prop or var name
#ns[@var]  // specifically a var name
#ns[$prop]  // specifically a prop name

I think that's a smart observation, that in most cases, it won't be ambiguous for the user which type of value they wish to return, as long as we document precedence rules.

And yet another problem I did not want to mention before because it's also quite a story on its own. I already mentioned that currently namespaces are not "open" to each other. And this actually introduces quite dramatic problems with "overriding" in a longer run. {example 2} The result is somewhat correct in fact, since when each #ns mixin is evaluated, its local variables have higher priority

I think what you mean is the evaluation order, no? I don't think it's intended to be "local variables win" (and I hope it's not documented that way) so much as mixins are evaluated and then merged. So a user to expect otherwise is to not know that mixins are evaluated before returning (even though that's consistent with other mixin behavior?)

So, yes, namespaces are not open to each other. However, Less already does this kind of "referential collapsing". That is:

#ns {
  .mixin() {
    one: two;
  }
}
#ns {
  .mixin() {
    three: four;
  }
}
usage {
  #ns.mixin();
}

The contents are not "open to each other". One reference is given, but both are called, evaluated, and returned. So, no, there's not one namespace. Whenever you referencing a "namespace" you're calling a collection. The same would be true with properties and variables.

#ns {
  @value: original;
}
#ns {
  @value: overridden;
}
usage {
  #ns(); 
}

The second #ns isn't really overridding the first variable. It's just effectively returning:

usage {
  @value: original;
  @value: overridden;
  value: @value;
}

So it's just straightforward Less evaluation. It's evaluating the mixin, then returning the variable. So this:

#ns {
  @value: original;
}
#ns {
  @value: overridden;
}
usage {
  value: #ns[value]; 
}

is effectively

usage {
  & {
    #ns();
    value: @value; 
  }
  @foo: @value;    // Error: unknown variable name "@value"
}

Make sense? Nothing new is changing in behavior, just a more convenient syntax. You're collapsing 4 lines into one.

seven-phases-max commented 8 years ago

It's just effectively returning:

usage {
  @value: original;
  @value: overridden;
  value: @value;
}

That's what I mean, if you change the example to

#ns {
  @value: original;
  @derived: @value + 1;
}
#ns {
  @value: overridden;
}
usage {
  #ns();
  value: @derived; 
}

it's not anymore equal to

usage {
  @value: original;
  @derived: @value + 1;
  @value: overridden;
  value: @derived;
}

I.e. for #ns[value]; to be usable we need first to collapse and then evaluate (while for mixins it's first evaluate then collapse).

matthew-dean commented 8 years ago

Oh, ok, that. But..... if someone is doing that, aren't they kind of idiotic isn't that weird? Why would you define a variable in a separate place and then try to pull a derived variable that's not in the same scope? Were there use cases that had those expectations? Sure, that specific use case might have an unexpected result, but... [shrug] ?

I'm just trying to think why we would need to address that specific case. I don't think we need to try to solve all of Less's weird scope edge cases just to make the syntax more convenient for 99% of use cases.

seven-phases-max commented 8 years ago
#ns[ident]  // either prop or var name
#ns[@var]   // specifically a var name
#ns[$prop]  // specifically a prop name

No, I actually did mean:

#ns[ident]  // either prop or var name
#ns[@var]  // still either prop or var (name specified by `@var` value)

Then answer to:

I don't see how that would be "common". In [] we're specifying what's being returned, ...

would be the question "where did we specify that (OK, docs) and does anybody read that there?" :) I.e. as always they will use their JS-intuition where

var foo = bar;
val = obj[foo]; // -> obj.bar;

thus they will expect

@foo: bar;
val: #obj[@foo];

to do the same (and then we'll have "Less is not JS, bla-bla-bla, RTFM").


But yes, if we won't agree on that my (quite radical, yep) variant, then:

#ns[ident]  // either prop or var name
#ns[@var]   // specifically a var name
#ns[$prop]  // specifically a prop name

is a good option.

matthew-dean commented 8 years ago

It's not quite the same as JS, because we're using unquoted identifiers. When we write this:

#ns[ident]  
#ns[@var]

...the equivalent in JS would be:

#ns["ident"]  
#ns["@var"]

It's just that CSS/Less allows plain keywords, so quoting here is not consistent.

seven-phases-max commented 8 years ago

...the equivalent in JS would be:

It's again only because you know what it's supposed to be. But they know only that JS var in ns[var] is a variable (not an ns member identifier), hence for Less they'll think ns[@var] means the same. :)

seven-phases-max commented 8 years ago

Back to evaluation:

Why would you define a variable in a separate place and then try to pull a derived variable that's not in the same scope?

Actually it is one of the primary use-cases for the namespaces.

// .....................................................
// framework:

#theme {
   @color: red;
   @background-color: #fff - @color;
   @secondary-color: lighten(@color, 25%);
   @alt-color: spin(@color, 45);
   // etc. etc.
}

.btn {
   color: #theme[color];
   background-color: #theme[background-color];
}

// .....................................................
// user app:

#theme {
    @color: blue;
    // here I expect all depended theme colors to change accordingly
    // (just like it *works* if I'd use global vars)
    // "Your namespaces are useless, guys! I'll stick to my global garbage" <- that's why it's important
}

P.S. There're workarounds as usual, but they are... well... workarounds (like this).

matthew-dean commented 8 years ago

Alright, that's possible lol.

So, do you have a proposed solution?

matthew-dean commented 8 years ago

But they know only that JS var in ns[var] is a variable (not an ns member identifier), hence for Less they'll think ns[@var] means the same

Maybe. There are a lot of people who know Less/CSS who aren't JavaScript developers.

But.... I think your more important valid point is that people will probably want the identifier to be variable. Which I see is why you you suggested retrieving prop or var without @, such that someone could dynamically retrieve a variable. My question would be, will that be confusing to some in the other direction? Or, if so, which one is less confusing?

Less has historically often chosen simplicity over complete non-ambiguity, such as auto-casting classes / ids as mixins. So ambiguity is not necessarily a failure, as long as the result is mostly intuitive and expected and can be clearly documented.

seven-phases-max commented 8 years ago

So, do you have a proposed solution?

For namespaces? The implementation side is trivial (just like we've noticed above it's "collapse then evaluate" instead of "evaluate then collapse"). The only question is how consistent this will look (as val: #ns[var]; will not be strictly equal to #ns(); val: @var;, thus we'll have a minor drift in "What is Less namespace?" definition kind of things).

seven-phases-max commented 8 years ago

So ambiguity is not necessarily a failure, as long as the result is mostly intuitive and expected and can be clearly documented.

Good point. It would be nice if we could get more opinions (at least just to make sure we don't miss some critical pitfall in this case).

stevenvachon commented 8 years ago

I'm not understanding the namespace idea, but here's what I see as being useful:

@var {  // similar to @keyframe
  key: value
}

element {
  property: @{var.key};
  // OR
  property: @{var, key};
}
seven-phases-max commented 8 years ago

I'm not understanding the namespace idea

It's exactly the same thing... In general, "namespace" can be considered as a synonym for a map-like-object too.

matthew-dean commented 8 years ago

@stevenvachon What @seven-phases-max said. Essentially it's treating a ruleset similar to a collection of key / value pairs. The only difference would be syntax.

@var: { 
  key: value
}; // currently requires semi-colon after final brace

element {
  property: @var[key];
}

The reason it wouldn't be "dot notation" is because you can already chain namespaces like:

#ns {
  .mixin() {
    prop: val;
  }
}
element {
  #ns.mixin();
}
calvinjuarez commented 8 years ago

Just saw that this discussion was moved here, so I'm adding my 2¢ because of the invitation there.

I super love where this is going.

#ns[ident]  // either prop or var name
#ns[@var]   // specifically a var name
#ns[$prop]  // specifically a prop name

Simple, intuitive, and unambiguous. I wonder, though, if the #ns[ident] option may be more dangerous than it's worth. For example, in the following:

#ns {
  @color: #5ad;
  color: @color;
  // OR
  red: #f00; // a fake property, used as a constant, to keep it distinct from "public" variables.
  @red: $red; // (I'm assuming `$` would also access properties in the immediate local scope)
}

// ...

.class {
  color: #ns[color]; // `@color` or `$color`?
  color: #ns[red];   // `@red` or `$red`?
}

Although I guess that could be resolved as Less usually resolves conflicts: just go with whichever it matches last (i.e. $color and @red in the examples above). Then, if developers find #ns[ident] too dangerously ambiguous, they can set their own rules in their code style guides and whatnot.

seven-phases-max commented 8 years ago

just go with whichever it matches last

I'd rather suggest property > variable priority (simply because historically it's properties that use variable values and not in opposite, but it does not really matter - i.e. variable > property would also be fine) and not the "shared LDW" simply because these are not really entities of the same level and not quite interchagable (see below). E.g. for your particular example both should result in #ns[property].

a fake property, used as a constant,

Btw., it's important to pre-alarm that while properties become available as variables because of #2433 and (in the "namespacing" context) possibly less verbose code (by skipping @), one who abuses them gets the evil Pinocchio to himself (e.g. in your example #ns {red: #f00} will appear in your CSS output as it also is an ordinal CSS style... Technically you'll need either #ns() {...} there or yet more abused (reference)). Counting other weird side-effects (some are mentioned in #2654, but there's more - mostly rising from the flat-scope nature of CSS itself), it's still highly encouraged to use variables for variables, unless a property is actually fits better in a particular snippet for a specific reason.

calvinjuarez commented 8 years ago

I can dig the "use variables for variables" deal, but I do think there's a need for private variables (not as a replacement for standard variables, but as a supplement, allowing for clarity between theming variables and internal-use variables in a framework). I think #ns() with "property-constants" is actually a good solution, though.

As for conflict resolution, I don't think it's a super likely case anyway, so I think either prop > var or vice-versa would be just fine. My first instinct would be var > prop, but I honestly can't really say why, so I'm not too fussed either way.

matthew-dean commented 8 years ago

I wonder, though, if the #ns[ident] option may be more dangerous than it's worth

The reasoning was that #ns[ident] was a bit cleaner for referencing properties, and actually matches the original syntax. But then @seven-phases-max had the idea to allow it to reference vars or props, which allows some flexibility if the author writes their private definitions as either. And then, for explicit needs, @ and $ exist.

That said, I actually think writing #ns[ident] to reference an @ident var is perhaps counter-intuitive. At this point, I would almost prefer #ns[@ident] for vars and #ns[ident] or #ns[$ident] for properties. I feel like that might more accurately reflect someone's "guesses" (although the latter for properties only becomes intuitive once we complete/merge property referencing).

Of course, we could , as I think was discussed in this thread (in a meeting so didn't have time to review all of it), document referencing as "vars: #ns[@ident], props: #ns[ident]" and still allow #ns[ident] to fallback to a variable lookup if a property isn't found. However, I'm not sure: would that make troubleshooting a more difficult? Theoretically, the number of properties / variables in a given rule should not be massive, so the variable fallback seems okay. And, if they have a property / var with the same name....

one who abuses them gets the evil Pinocchio to himself

Basically this.

calvinjuarez commented 8 years ago

the number of properties / variables in a given rule should not be massive

In a framework like Bootstrap (which has 375 variables in v3.3.6) the authors may not namespace groups of variables within their framework's main namespace, so I don't think we can be sure that having loads of props/vars in a namespace is necessarily gonna be all that uncommon. But it does make more sense to let the developer be responsible for keeping his/her own code unambiguous.

document referencing as "vars: #ns[@ident], props: #ns[ident]" and still allow #ns[ident] to fallback to a variable lookup if a property isn't found.

I think, if behavior is intentionally included, it should be documented. Whether strictness or flexibility is the higher value, the docs should inform developers what they should expect from their code. For example:

"You can access a variable or property with #ns[name]. If a variable and a property share the same name, Less will prefer the property over the variable. To avoid conflicts like these, you can also specify that you're accessing a variable by prepending @ to the name (e.g. #ns[@name]). Likewise, property accessing can be signified using $ in the same way (e.g. #ns[$name])."

TL;DR Whatever the behavior is in the end, as long as it's clearly and fully documented, I don't think people will run into too many issues.

seven-phases-max commented 8 years ago

"You can access a variable or property with #ns[name]. If a variable and a property share the same name, Less will prefer the property over the variable. To avoid conflicts like these, you can also specify that you're accessing a variable by prepending @ to the name (e.g. #ns[@name]). Likewise, property accessing can be signified using $ in the same way (e.g. #ns[$name])."

+1

matthew-dean commented 8 years ago

"Less will select" rather than "Less will prefer" since a preference means it could opt to not do that, but that's just a grammatical tweak. ;-) Otherwise, yep.

seven-phases-max commented 8 years ago

Will return (the value of).

Well, the exact wording may be polished infinitely (e.g. "To avoid conflicts like these" -> no negative words in the docs -> so just "You can also explicitly specify if you're referring to a property or a variable by using @ident/$ident... "Prepending ..." is also awkward there even if not so incorrect - for a programming language syntax constructions we usually do not think of turning something into something by prepending or appending some symbols... etc.)

matthew-dean commented 8 years ago

One thing not specifically addressed (I don't think, scanning back over the thread): With this change in namespace referencing, do we still want to keep $prop for local property references? Because we could also do [prop]. More consistent with this syntax? Never mind, see below.

matthew-dean commented 8 years ago

Leading back to a point @seven-phases-max made a while ago about variable value / reference ambiguity.... I'm wondering if we shouldn't but the reference in quotes, indicating it's a return value that we want. So maybe we would keep $prop so that you could do this:

@varToGet: '@default-color';
.foo {
  color: @defaults['@default-color'];
  color: @defaults[@varToGet];
  also: #ns1['property'];
  or: #ns1[#ns2$propertyToGet];
}
@defaults: {
  @default-color: red;
};
#ns2 {
  propertyToGet: 'property';
}

For local values we could do the same:

.foo {
  color: red;
  background-color: $color; // or
  background-color: ['color']  // or
  background-color: [@get-property];
  @get-property: 'color';
}

That would add a lot of flexibility to the syntax. And I think it's more logically consistent, yes?

matthew-dean commented 7 years ago

Here are two other alternative syntaxes (to the above proposal) which could achieve the same thing, as other ideas. These are a little more concise, with just a little more ambiguity.

@varToGet: '@default-color';
.foo {
  color: @defaults[@default-color];
  color: @defaults[@@varToGet];
  also: #ns1[foo];
  or: #ns1[@#ns2[propertyToGet]];
}
@defaults: {
  @default-color: red;
};
#ns1 {
  foo: bar;
}
#ns2 {
  propertyToGet: foo;
}

And, going back to the "identifier" idea applying to vars or props.

@varToGet: '@default-color';
.foo {
  color: @defaults[default-color];  
  color: @defaults[@varToGet];
  also: #ns1[foo];
  or: #ns1[@#ns2[propertyToGet]];  // ¯\_(ツ)_/¯
}
@defaults: {
  @default-color: red;
};
#ns1 {
  foo: bar;
}
#ns2 {
  propertyToGet: foo;
}

Not sure of the 3, which is:

  1. The most "Less-like"
  2. The most readable
  3. The least ambiguous

Opinions welcome. #ns1[foo] and @defaults[default-color] still aren't bad, as they're much more concise than quoted values, but the question is about ambiguity and how intuitive they are.

seven-phases-max commented 7 years ago

(Assuming the last one is that #3). #3 is more friendly because its less verbose (so I like it more), but #2 is slightly more consistent with @@fnord (not sure if this minor(?) consistency is really important).

calvinjuarez commented 7 years ago

#2 is slightly more consistent with @@fnord

Yeah, Nº 2 would be my vote for that reason.


As a side note I'd assume #ns1[@#ns2[propertyToGet]] in Nº 2 would resolve this way:
#ns2[propertyToGet] ➡️ foo, so
@#ns2[propertyToGet] ➡️ @foo, so
#ns1[@#ns2[propertyToGet]] ➡️ #ns1[@foo] ➡️ undefined.

In other words, [@...] looks for a variable, [$...] would look for a property, and [...] would look for either (selecting property over a variable, if there's ambiguity). That'd keep [@@var] sensible enough.

It would also introduce stuff like [@$foo], [$@foo], and [$$foo] (for better or worse; I like it).

seven-phases-max commented 7 years ago

It would also introduce stuff like [@$foo], [$@foo], and [$$foo] (for better or worse; I like it).

Btw., I thought of it too (but decided to not mention as it may open another pandora box :) If we allow just [...] (meaning just "current namespace/scope"), we can technically deprecate @@foo in favour of [@foo] and then #3 wins :P. (Honestly these double cryptosymbols make the code to look like a swearing).

matthew-dean commented 7 years ago

As a side note I'd assume...etc

Ah yes, you are correct sir. It is returning a property, which then we are using to dynamically select a property. Should be #ns1[$#ns2[propertyToGet]], yes?

At first I thought of needing to quote the index, but then that seemed inconsistent with Less / CSS, which doesn't use quotes in references for keywords / identifiers, or indeed, properties / values (such as in @supports). It's already encased in brackets, so I don't think it's that much confusion.

But yeah, I thought the variable-variable syntax (or property-variable) makes some sense if people want to dynamically return a value. So I guess I'm proposing we be explicit with variable names and property names rather than the idea we were tossing around about #foo[bar] returning either @bar: blah or bar: blah depending on whatever precedence rules we decide. What do you think about that?

seven-phases-max commented 7 years ago

#ns1[$#ns2[propertyToGet]]

I'd suggest this stuff to be left undefined yet. The problem is that both $#ns2[propertyToGet] -> $foo and @#ns2[propertyToGet] -> @foo assume that we can get the particular entity by text-concatenation, but it never works like this in Less in general. E.g. we don't expect something like @e("foo") to mean @foo. I.e. I think it's fine to declare that if you want to get either $foo or @foo then the propertyToGet should be one of either (not counting of course that foo already works just fine). After all if you're using some "external" data for indexing another "external" data, it's quite weird to assume you'd need some "local" mutation to hack this.

@#ns2[propertyToGet] (as in @...[] not as prepend @ to some value) may be of course thought as some specific statement, but then neither variant of "symbol-concatenation-based-logic" should be considered as its only possible semantics.

matthew-dean commented 7 years ago

I'd suggest this stuff to be left undefined yet.

I'm fine with that. I was spitballing possible things that people might try, but if we only define usage with specific simple examples, that should be fine. It looks pretty crazy anyway. If we allow a property name, a variable name, and a variable variable, that's still pretty damn flexible and useful.

matthew-dean commented 7 years ago

So, it sounds like the favored syntax (and usage) is something like:

@varToGet: default-color;
.foo {
  color: @defaults[@default-color];  // or
  color: @defaults[@@varToGet];
  prop: #ns1[foo];  // only property
  var: #ns1[@foo];
  sub: #ns1.vars[sub]; // consistency with current namespacing of mixins
}
@defaults: {
  @default-color: red;
};
#ns1 {
  foo: bar;
  @foo: bar;
  .vars() {
    sub: value;
  }
}
calvinjuarez commented 7 years ago

How would you access a property in the same scope? $prop, still?

I think it makes sense to drop the flexibility. I'd hope that #ns1[$foo] would also be allowed, though, if same-scope property reference can be done with $foo, because I like the neatness/parity of @ for variables and $ for properties. (Unless $ is being dropped entirely.)

Another option would be just not to allow accessing inside an accessor. So, like, no [] inside []. In that case though, I imaging folks would end up trying to do stuff like this:

#ns1 {
  @color: #fff;
}
#ns2 {
  var-name: color;
}

.thing {
  @get: #ns2[$var-name];
  background-color: #ns1[@@get];
}

But if I'm understanding correctly, that's not how @@ works, right? Or would that work?

matthew-dean commented 7 years ago

How would you access a property in the same scope? $prop, still?

Yes.

I think it makes sense to drop the flexibility. I'd hope that #ns1[$foo] would also be allowed, though, if same-scope property reference can be done with $foo, because I like the neatness/parity of @ for variables and $ for properties. (Unless $ is being dropped entirely.)

I think both could easily be supported, to serve people thinking about it in different ways.

Another option would be just not to allow accessing inside an accessor. So, like, no [] inside []

That would make documentation and parsing easier. I'd support that and with the final proposal I made, I was leaning more that way. Just simplify the whole thing and not try to cover all edge cases.

But if I'm understanding correctly, that's not how @@ works, right? Or would that work?

Yeah, that's not quite right. The @@ in this case would be when the name of the variable to retrieve is variable. Or in this proposal. So, with your reference the variable to be retrieved would be named @#ns2[$var-name]. I think there's enough flexibility in this syntax without pointer to pointers, and mixins can cover other cases.

EDIT: I wasn't properly reconciling the work in #16. Your example is actually correct, as far as I can tell now.

calvinjuarez commented 7 years ago

Well, I'm sold!

matthew-dean commented 7 years ago

We need a proper feature write-up for the final consensus to post to less/less.js. Can someone take this on?

calvinjuarez commented 7 years ago

On it.

matthew-dean commented 7 years ago

One question that came up for me doing some work on Less the last few days.

$prop is not documented yet. So to this question:

(Unless $ is being dropped entirely.)

I haven't reviewed the thread on property accessors to know if this was raised / rejected, and if rejected, why. But that was before the details on namespacing were fleshed out. So... Would this make more sense?

.box {
  color: red;
  border-color: [color];
}

vs. current syntax:

.box {
  color: red;
  border-color: $color;
}

Thoughts?

calvinjuarez commented 7 years ago

I feel like I remember it being brought up at some point, but I can't remember where. I'll say that I like $. I admit it brings up issues of parallels with @ for vars where they aren't currently planned, but I like it better. I think having $ allows for explicit-ness when accessing properties via namespace. #ns[@thing] vs. #ns[$thing]. (And, as a side-note, I don't really see why properties should be/behave any different to variables anyhow.)

I do get the motivation for [prop], and it does subvert parallelism issues (such as @@ parallels, like @$ or $$). It also would leave $ open for something like a special selector function/variable syntax, which has been thrown around in a bunch of issues.

But then variable accessing feels like it's piggy-backing on property accessing. Not that that's bad, I guess. But in the end, I still think I prefer $.

Those are my thoughts.


For reference, this is the thread where that syntax emerged: https://github.com/less/less.js/issues/1848

More reference: It looks like this idea was sort of approached/orbited back in this comment earlier in this thread: https://github.com/less/less-meta/issues/12#issuecomment-212995338

matthew-dean commented 7 years ago

Those are a good thoughts. There was a lot of discussion to get it to $, so if there's not a compelling reason to change it, then that's fine.

On this:

(And, as a side-note, I don't really see why properties should be/behave any different to variables anyhow.)

In the property accessor implementation, it's pretty similar to vars. Nearly identical. The main difference of course is that all property declarations end up in the final ruleset. But property accessors essentially "select" the property value using the same rules as variables, with the last one in the ruleset winning.

matthew-dean commented 7 years ago

More on properties: Originally, values assigned to variables were parsed into their individual nodes, whereas property values were essentially anonymous node strings, I think if they didn't contain any variables or functions. There was a (rightful) concern that adding property accessors would inflate the time / size of node parsing, since all nodes would be granularly parsed.

What I did in 3.0 was add "late parsing" to the "late evaluation" model of Less. So, now, in the eval stage, nodes can run strings back through the parser if some operation needs to be performed. So that allowed properties to act like variables without instantly making the parse tree more complex with more nodes.

seven-phases-max commented 7 years ago

So... Since css-grid feature (already supported in most of browsers) grabbed the [identifier] syntax :( I guess the feature needs a new syntax... any ideas? Technically it does not have to be a begin/end-char thing (like [] or ()), a single (or whatever-count) begin char (= subscription operator, e.g. ns?member, ns->member) will fit too (though a begin/end marker intuitively looks like a more safe thing).

matthew-dean commented 6 years ago

Since css-grid feature (already supported in most of browsers) grabbed the [identifier] syntax

Wait what??

matthew-dean commented 6 years ago

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout/Layout_using_Named_Grid_Lines

🤔 Hmm...... @seven-phases-max as far as I can tell, there isn't any conflict here. The Less parser would require a preceding identifier (with no space) #id[prop] or .id[prop] or @dr[prop]. I proposed in this thread that we could allow a naked [prop], but we already have $prop, so I think we already dodged a bullet.

The CSS Grid syntax won't allow #id[prop] and Less wouldn't allow [prop] (at this point), so there's no overlap. I can't see a scenario where that would change. I could eat my words, but that's the way I see it right now.