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

Variable / property namespacing #12

Open
matthew-dean opened this issue Jan 30, 2016 · 71 comments
Open

Variable / property namespacing #12

matthew-dean opened this issue Jan 30, 2016 · 71 comments

Comments

@matthew-dean
Copy link
Member

The discussion around namespacing had some proposals on syntax, but I'm starting to think less/less.js#2767 makes the proposal problematic. less/less.js#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
Copy link
Member Author

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
Copy link
Member

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
Copy link
Member Author

@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
Copy link
Member

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
Copy link
Member

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
Copy link
Member

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
Copy link
Member Author

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
Copy link
Member

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
Copy link
Member Author

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
Copy link
Member

#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
Copy link
Member Author

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
Copy link
Member

...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
Copy link
Member

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
Copy link
Member Author

Alright, that's possible lol.

So, do you have a proposed solution?

@matthew-dean
Copy link
Member Author

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
Copy link
Member

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
Copy link
Member

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

@matthew-dean matthew-dean changed the title Variable / property namespacing and interpolation syntax Variable / property namespacing - 3.0 discussion Jan 31, 2016
@stevenvachon
Copy link

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
Copy link
Member

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
Copy link
Member Author

@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
Copy link
Member

calvinjuarez commented Apr 21, 2016

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
Copy link
Member

seven-phases-max commented Apr 21, 2016

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
Copy link
Member

calvinjuarez commented Apr 21, 2016

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
Copy link
Member Author

matthew-dean commented Apr 21, 2016

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
Copy link
Member

calvinjuarez commented Apr 21, 2016

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.

@calvinjuarez
Copy link
Member

calvinjuarez commented Oct 10, 2017

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: less/less.js#1848

More reference: It looks like this idea was sort of approached/orbited back in this comment earlier in this thread: #12 (comment)

@matthew-dean
Copy link
Member Author

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
Copy link
Member Author

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
Copy link
Member

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
Copy link
Member Author

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

Wait what??

@matthew-dean
Copy link
Member Author

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.

@matthew-dean
Copy link
Member Author

Added (failing) tests for namespacing. No work has been done / started on the parsing / evaluation side. less/less.js@3642ca9

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 4, 2018

Yes, there's no major conflict ... yet. Well, not counting CSS requires no space there, so if this goes beyond grid properties having no colors, in compressed CSS it could possibly appear#abc[foo] some day (by now it could only be very exotic .42[bar]).

My main concern though is a very different semantics of similar looking values in the same context
(If I'm not mistaken, there was no such precedent yet beside /). Sure something like:

grid-template-rows: [main-start] .main[content] [content-start];

looks pretty exotic so far but who knows where they will end with these [] eventually.

--
P.S. There's also #ns[var][var][var] potential in out syntax (if nested variables are DRs) and it goes even more close to some compressed grid values.

@matthew-dean
Copy link
Member Author

matthew-dean commented Feb 6, 2018

🤔

No, I see where you're coming from, just from the readability standpoint. The no spaces required is not as big a deal but..... I understand it's avoidable. Hmm.........

Okay, well we've got some choices here....

  • Parentheses () are out. Math.
  • Brackets seem out (damn you CSS Grid)
  • Curly braces are.... problematic... There's some parsing back-tracking required, unless there's some special combo of chars 🤔
  • ns->member seems like a non-starter, since the name space can be .class-, can it not? So we'd be confusing the - as the end of the NS with -> as referencing the prop/var. Unless.......

Some possibilities:

  1. .ns>>prop .ns>>@var -- So like an extension of the #ns>.mixin idea, piercing further into the rules? However .ns>>@dr>>prop gets kinda messy-looking
  2. .ns[{prop}]? [shrug]
  3. @{.ns[prop]} - extend interpolation syntax to wrap any identifier lookup? (Which is basically where we started, but I'm proposing a bit of a syntax merge here.)
  4. ${.ns[prop]} - same as above but muddies the waters less. You end up with $prop ${prop} ${@var} ${.ns[prop]} ${.ns[@var]} ${.ns[@dr][prop]}

#1 is maybe cleanest, but still potentially ambiguous. As I wrote out #4 it seems to solve a lot of on the ambiguity side, and seems to logically connect with property lookup in a decent way. You end up with a clearer sentence with:

grid-template-rows: [main-start] ${.main[content]} [content-start];

Also, #4 has the problem of string/selector interpolation already baked-in and solved. Kinda win-win. This basically merges the ambiguous syntaxes ${ns prop} and @{ns var} with the more explicit ns[@var], so that you end up context-safe, and also clarity of syntax: ${ns[@var]}

Thoughts?

@matthew-dean
Copy link
Member Author

matthew-dean commented Feb 6, 2018

Note, #4 also solves this problem I wrote out that kicked off the thread:

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

Instead of trying to do some kind of magical transposition of applying the leading @ onto the internal identifier, you end up with:

val: ${@dr[@var]};  // It's clear the identifier is @var within @dr and `${}` is a referencing lookup

And then you can grab multiple levels deep:

val: ${@dr1[@dr2][@var]};

And then if we do the mixin aliasing feature here, we could end up with something like.....

#usage {
    @theme: ${.my-theme-ns(red)};
    @theme.some-mixin();
    border: 1px solid ${@theme[@secondary]};
}
// Unless only this would be required; we can work that out separately
//#usage {
//    @theme: .my-theme-ns(red);
//    @theme.some-mixin();
//    border: 1px solid ${@theme[@secondary]};   -- ${} only needed for single values, not rules?
//}

The last one is spitballing, I just wanted to make sure we weren't screwing up that thread.

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 7, 2018

${ns[@var]}

Honestly? I always hated this @{} syntax the most in Less. I'd rather prefer #ns->identor #ns<ident> then. Sure, this is more like irrational hate (having just a bit of rationality for "too verbose", "too {}-bock-like looking), counting @{} is already there and nothing can be done with it. But, yet again to be really honest and whining, I'd really agree for any alternative instead (incl. conflicting #ns[ident] or ugly @ns|ident) :).

@rjgotten
Copy link

rjgotten commented Feb 7, 2018

Regarding option 1 of .ns>>prop:
The CSS Selectors Level 4 spec introduces >> as an explicit descendant combinator, so you probably want to leave it alone.

I'd like to suggest a 5th option: .ns[[prop]]
Afaik that will suitably disambiguate it from grid templates.

The @ns|ident suggestion is actually nice as well, imho; reminiscent of XML namespaces in CSS selectors.

@seven-phases-max
Copy link
Member

The @ns|ident suggestion is actually nice as well

:)
Curiously I also realized it might be not so bad when I see it with that particular font above, but in other fonts it's much more weird, e.g.: .ns|intend (looks like .nsjintend/.nslintend).

@matthew-dean
Copy link
Member Author

@seven-phases-max

Honestly? I always hated this @{} syntax the most in Less. I'd rather prefer #ns->identor #ns then. Sure, this is more like irrational hate (having just a bit of rationality for "too verbose", "too {}-bock-like looking), counting @{} is already there and nothing can be done with it. But, yet again to be really honest and whining, I'd really agree for any alternative instead (incl. conflicting #ns[ident] or ugly @ns|ident) :).

Haha fair enough, and I came back today to be like, "uuuuugh do we REALLY have to wrap all namespaced properties"?

For the same reason, .ns[[prop]] seems like an annoying level of wrapping.

I never considered #ns<ident>. That's cool and nothing immediately strikes me as bad about it.

Usage:

.test {
  val: @dr<@var>;
  val: @dr<@dr><@var>;
  grid-template-rows: [main-start] .main<content> [content-start];
}

vs.

.test {
  val: @dr|@var;
  val: @dr|@dr|@var;
  grid-template-rows: [main-start] .main|content [content-start];
}

🤔 The pipe | I wonder if it's almost too gentle of a separator, similar to your instincts. But on the other hand, multiple >< starts to look a little busy (although that's gonna be the more rare use case.

🤔 🤔 🤔 I dunno, the pipe is kinda fucking cool. Very clean result. vs.....

.test {
  val: @dr->@var;
  val: @dr->@dr->@var;
  grid-template-rows: [main-start] .main->content [content-start];
}

HMMMMMMMM... the explicit nature of -> very strongly conveys the semantic meaning of a retrieval here, and adds more clarity in the grid-template-rows value. And maybe the limitation of "don't end identifiers with a hyphen" (which, who would do that anyway?) is no big deal.

And -> is more tied in languages to map access than | probably. Like, if we're not doing brackets [] then it's probably the #2 most used, no?

So yeah, I would agree with all 3 of these options over ${ns[ident]}. These are good suggestions.

@matthew-dean
Copy link
Member Author

Ha, I noticed after posting that Github did some good code coloring with <> and -> whereas pipes look kinda dead. This is minor, but enough to probably bump | more solidly to last place on that list of 3 for me.

@matthew-dean
Copy link
Member Author

The nice thing about -> is that you can use legacy mixin syntax to do:

.test {
  val: #ns > .mixin -> @var;
}

vs.

.test {
  val: #ns > .mixin  <@var>;
}

That is, when space-separated, the latter starts to look more strange and inconsistent.

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 8, 2018

These -> and <...> were just random examples of course (I simply borrowed from C++) - I did not really considered them seriously. Sure, if we go deeper they reveal to be not very suitable, too ambiguous even if there's no direct conflicts:

  • .ns->ident - is it a member access or .ns- greater than ident?
  • .ns<ident> another - is it boolean (.ns < ident) > another?
    etc.

So please do not treat these as some kind of proposal (they were really just random inspirational goodies to stress individual habits. :)


That is, when space-separated, the latter starts to look more strange and inconsistent.

That's another story. There're reasons (and this is one of them) why in either C-like language you (ideally) always write member-access and subscript operators w/o spaces. E.g. in JavaScript:

let a =objects[12  ].  memberArray [ 42]
;

is valid (if I'm not mistaken) but we never write it like this :) (it's always obj.member and array[index] thus the legacy #ns > .mixin-like stuff (when it's a namespace.member thing and not a CSS selector at all) is really a bogus formatting style).

@matthew-dean
Copy link
Member Author

These -> and <...> were just random examples of course (I simply borrowed from C++) - I did not really considered them seriously. Sure, if we go deeper they reveal to be not very suitable, too ambiguous even if there's no direct conflicts:

So please do not treat these as some kind of proposal (they were really just random inspirational goodies to stress individual habits. :)

[sigh]

Okay. Well... suggestions?

@seven-phases-max
Copy link
Member

I still do like #ns[ident] thing the most so far. I was just wondering if we could invent something comparable considering the new [] baby of CSS. It seems we can't :)
Technically I think if you've already started experimenting with implementation it's fine to go with [] and if necessary it can be changed to something else (#ns≥iden≤) even after RC.. I assuming the most tedious part there will be the evaluation code while the parsing part (at east what for what comes after #ns/.ns) is relatively easy (thus not so critical to later changes).

@matthew-dean
Copy link
Member Author

matthew-dean commented Feb 9, 2018

I still do like #ns[ident] thing the most so far. I was just wondering if we could invent something comparable considering the new [] baby of CSS. It seems we can't :)

lol @seven-phases-max sometimes it's hard to glean what your actual position is. 😉

Okay..... if #ns[ident] works in the short term, let's do it. No, I haven't started any implementation and I'd really prefer to not do that work myself (unless I can finally convince my team at work to switch from Sass to Less, which.... no luck so far). Basically all I did was start a new branch, and check-in the tests that would need to pass based on that syntax.

@seven-phases-max
Copy link
Member

seven-phases-max commented Feb 9, 2018

lol @seven-phases-max sometimes it's hard to glean what your actual position is.

:)
Well, it's just like you start to think of it and come to a conclusion that if it's become too ugly/verbose or too conflicting then at some point you may find yourself preferring something like (already working code):

div {
    color: some-ns(primary); // == .some-ns[@primary]
}

.some-ns {
    @primary:   red;
    @secondary: blue;
    // etc.
}

// map namespace to function using corresponding plugin:
.function-some-ns(@ident) {
    .some-ns;
    return: @@ident;
}

which will be sad counting the efforts (to be) taken.
So you're keep trying to find better ways (one would expect from the native impl.) till the very end.

@matthew-dean
Copy link
Member Author

Please check out my PR! less/less.js#3242

@matthew-dean
Copy link
Member Author

Question: could we not apply the concept of lookups to lists? That is, why not allow it as a shorthand for:

@items: 1 2 3;
@value: extract(@items, 2);  // old
@value: @items[2];  // new

I mean, the evaluation is really not that hard. Most of the work has been done. So, like JavaScript, it could refer to index-based or key-based lookups. The only confusion might be that I think Less lists are 1-based, not 0-based? But I think I would still keep it consistent with whatever extract() is doing.

@calvinjuarez
Copy link
Member

Saying @itemsArray: one two three; is like an "Array" and @itemsObject: { @one:one; @two:two; @three:three; } is like an "Object" seems like a nice parallel to me. It might be a bit weird, but I can't think of any direct objection, except that we're adding a relationship between two features that weren't all that related. Though the relationship kind of exists, we'd just be formalizing and unifying it.

I think I'd want to deprecate (¿or maybe just discourage?) extract() in that case, so the question doesn't become "Why can't I extract(@itemsObject, @one)?".

Got my 👍, but I don't think it's super critical, unless it turns out to be low-hanging fruit.

@matthew-dean
Copy link
Member Author

@calvinjuarez Well it parallelizes other languages like PHP and JavaScript which overlap arrays / maps / objects in the way they're accessed. So yeah, if we imagine every list as having a key/value, just that flat lists like one two three, the keys for that are just 1 2 3 (since Less lists are 1-based). So all lists have keys, we just introduce that some are their indices, and some are keywords / names.

except that we're adding a relationship between two features that weren't all that related.

They weren't related, until I started to use rulesets as maps for writing up 3.5 examples. Once you start to think of rulesets as just data, which may produce rules, but may be considered a map of values, then lists start to take on a semantically similar vibe.

What I mean is, in Less, a "list" may just be a property value of a sequence of keywords, or it may be "used" as data for some Less functional purpose. In other words, Less already "re-purposes" CSS structure to produce "data".

Trust me, start using 3.5 syntax and you'll see what I mean lol.

@matthew-dean
Copy link
Member Author

matthew-dean commented Jul 3, 2018

Btw, I ran into this today related to this comment from @seven-phases-max.

To summarize his comment: mixins technically merge their rules together, and that includes their variables. But the values of mixins get evaluated before merge. So even before 3.5, if you wrote:

.mixin() {
  @value-1: 1;
  @value-2: @value-1;
}
.mixin() {
  @value-1: 2;
}

.foo {
  .mixin();
  bar: @value-2;
}

// output

.foo {
  bar: 1;
}

This problem isn't resolved in 3.5, bar: .mixin[@value-2] would still produce 1 as a value.

To be honest, I don't know that we want to change the behavior, especially with the goal of making vars private in the #16 discussion. But we may need to be careful to document that overriding a value for a variable will not change the computed value of other vars. Instead, users should be encouraged to evaluate the computed values at call time.

I basically "re-discovered" this problem when tinkering with the Less library that I'm using to test out this feature.

@calvinjuarez
Copy link
Member

calvinjuarez commented Jul 14, 2018

@matthew-dean No, no, to reiterate, I have no objections. In my comment I'm stating essentially "Yeah, this all makes a lot of sense". I was thinking inline, though, so it's a little meandering. Sorry about that. I'm on-board.

As to the value evaluation, I think that's a quirk and feature of Less. I'm not sure I'd want it changed.

.mixin() { // default
  @var: foo;
}
.mixin() when (@i-want-var-to-be-bar) {
  @var: bar;
}

//...
@i-want-var-to-be-bar: true;

.selector {
  -less-show: .mixin()[@var];
}
.selector {
  -less-show: bar;
}

Right?

Update:

So, then,

.mixin() { // default
  @var: foo;
  -less-show: @var;
}
.mixin() when (@i-want-var-to-be-bar) {
  @var: bar;
}

//...
@i-want-var-to-be-bar: true;

.selector {
  .mixin();
}
.selector {
  -less-show: bar;
}

They're essentially the same, right?

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

5 participants