Ampersand and the cartesian product

32 views
Skip to first unread message

Pierre-Yves Gérardy

unread,
Mar 11, 2016, 7:06:59 PM3/11/16
to Sass
Hello Sassy folks,

I'm working on a CSS in JS lib (a JS object to CSS compiler, not too far from SASS in JS, actually, since I provide nested selectors and at-rules like you do), and I was wondering on the reason behind the cartesian product done by the & operator omre than once. Specifically:

p, a {
  & > & {/*...*/}
}

compiles to

p > p, p > a, a > p, a > a {/*...*/}

rather than a naive

p > p, a > a {/*...*/}

Is there a practical use case for that feature?

It complicates the implementation a bit, and since I want my lib to be usable in the browser, every byte and every clock cycle count (currently it clocks at 2.2KB mingzipped).

When in doubt, I try to follow SASS's lead, since it's AFAIK the most popular CSS preprocessor, but I'm not sure this very feature was that instrumental in SASS's success... For the record, LESS also does the cartesian product while Stylus uses the naive method.

Thanks in advance for your replies,
—Pierre-Yves

Natalie Weizenbaum

unread,
Mar 11, 2016, 8:15:36 PM3/11/16
to sass-lang
This is a question of the underlying semantics. We define "&" to be the parent selector list. That means that when you write "& > &" in your example, it means "(p, a) > (p, a)". The expansion of this is the cartesian product you're seeing.

To put it differently, when you write "&" we treat that as though it were a selector that matches the same elements that are matched by the parent selector. In this case, those are any "p" or "a" elements. When you write "& > &", that means "match any & that's nested directly beneath an &", which expands to "match any 'p' or 'a' element that's nested directly beneath a 'p' or 'a' element".

I'm sure you could come up with a different set of semantics that produce the output you're expecting—something like "for each complex selector in the parent selector list, create a copy of the child selector in which & matches the same elements as that complex selector, then join those into a selector list"—but that's a lot more complex, and it doesn't treat "&" as a selector with normal selector semantics. Semantic simplicity is valuable in and of itself, even when not all of the consequences are what you'd choose in a vacuum.

--
You received this message because you are subscribed to the Google Groups "Sass" group.
To unsubscribe from this group and stop receiving emails from it, send an email to sass-lang+...@googlegroups.com.
To post to this group, send email to sass...@googlegroups.com.
Visit this group at https://groups.google.com/group/sass-lang.
For more options, visit https://groups.google.com/d/optout.

Pierre-Yves Gérardy

unread,
Mar 11, 2016, 8:59:25 PM3/11/16
to Sass
Thanks for your reply.

For the naive version what about: "For each complex parent p, use the child as a template where & is replaced by p"? (Basically paraphrasing your definition without detailing the implementation).

I'm currently doing the cartesian product, but I'd like to know if there are concrete, practical examples of its use in the wild. The main reason I want & is to enable users to do use things like .ie &. The ampersand is facultative in leading position (in my lib).

In the simple case I could splitAmpersand(selector).join(parent) (where splitAmpersand partially lexes the selector to skip strings and comments). That's what I was doing before I discovered how SASS behaves in that regard.

Natalie Weizenbaum

unread,
Mar 25, 2016, 9:00:52 PM3/25/16
to sass-lang
On Fri, Mar 11, 2016 at 5:59 PM, Pierre-Yves Gérardy <pyg...@gmail.com> wrote:
Thanks for your reply.

For the naive version what about: "For each complex parent p, use the child as a template where & is replaced by p"? (Basically paraphrasing your definition without detailing the implementation).

When you start talking about templating, you get into the realm of text-munging. Although at a low level this is what preprocessors do, users tend to be much more comfortable thinking about semantics. That's why I phrase my definitions in terms of what elements are matched by which selectors, rather than the syntactic structure of the selectors themselves.

I'm currently doing the cartesian product, but I'd like to know if there are concrete, practical examples of its use in the wild. The main reason I want & is to enable users to do use things like .ie &. The ampersand is facultative in leading position (in my lib).

I don't have any off the top of my head. In general I consider writing & multiple times in a single selector to be a little suspect. But in this case, concrete uses are beside the point: there's inherent value in a consistent set of semantics that match the way the user approaches the problem. Even if those semantics produce edge cases that don't seem immediately useful, the ability for users to consistently reason about them is worth it.

In the simple case I could splitAmpersand(selector).join(parent) (where splitAmpersand partially lexes the selector to skip strings and comments). That's what I was doing before I discovered how SASS behaves in that regard.

Again, this is thinking in terms of textual manipulation rather than selector semantics. 
Reply all
Reply to author
Forward
0 new messages