Less.js: Allow parametrized mixins as detached rulesets to form 'lambdas'

Created on 4 Nov 2014  ·  118Comments  ·  Source: less/less.js

It seems that currently LESS only supports 'pure' rulesets to be passed along as mixin arguments or stored in variables as detached rulesets.

I suggest to extend this support to incorporate parametrized mixins, essentially giving LESS the capability to work with lambdas.

E.g.
One would be able to write

.list {
  .forEach(foo bar baz, (@item, @index) {
    @i : (@index + 1);
    > li:nth-child(@{i}):before {
      content : "@{item}";
    }
  });
}

where .forEach is defined as

.forEach(@list, @lambda) {
  @n : length(@list);

  .for(0)
  .for(@index) {}
  .for(@index) when (@index < @n) {
    @lambda(extract(@list, @index), @index);
    .for(@index + 1);
  }
}

Lambda mixin support would also neatly resolve recurring issues with function return arguments and the ugly hack where variables 'bubble up' to parent scope if said variables are as of yet undefined in said parent scope.

The suggested practice could become to adopt continuation style programming; passing 'return values' along into a lambda mixin to continue down the scope chain. This kind of mechanism is more transparent to users, less brittle by avoiding issues with potential variable name collisions and just fits in better with the overall functional programming paradigms that the LESS syntax is built on.

[EDIT]
Having just had a look at the way detached rulesets and calls are implemented in the AST, I think very little needs to happen to make this work. Even on the parser side of things, it seems fairly simple to just parse an optional block of mixin.args before blockRuleset in the detachedRuleset parser function and pass the arguments along to the tree.DetachedRuleset node instance. (The tree.DetachedRuleset would need to be extended with the params evaluation from tree.mixin.Definition, ofcourse.)

feature request medium priority needs decision

Most helpful comment

$@ works!? I had no idea! I thought it was nixed! That makes me so happy!

Ha, yeah I slipped it in bug fixes before I released 3.5. ^_^

All 118 comments

the ugly hack where variables 'bubble up' to parent scope

Just in case this is not ugly hack but well defined language facility.
(I also can't get the idea of solving "up-chain scope issues" with "down-chain parameter pass feature", these do not seem to be related, do they? But well, never mind, the feature remains useful regardless of this anyway)


Well, either way the idea itself is quite self-revealing and the only problem is ambiguous syntax, notice:

@a: {1: 1};      // OK, ruleset
@b: (@a);        // OK, set @b to @a
@c: (@a) (@b);   // OK, array/list
@d: (@a) {2: 2}; // ?!, array or unnamed ruleset with one parameter?
// same goes if this to be used as a mixin arg

In other languages with such facility this problem is solved with some specific keyword/operators preceding such lambda (e.g. [] in C++ or just function in JavaScript).

Just in case this is not ugly hack but well defined language facility.

When I mention it being a 'hack', I specifically mean that it seems hacked into the existing language desgin (to facilitate return values as a later addition), rather than something that was thought about up front. I don't mean the actual code is of low quality. Actually; the LESS parser and AST are quite well done. (Tip of the hat for that.)

However, the fact that it's well-defined and well-engineered doesn't make it less ugly when used. It's a language feature that is completely counter to intuition for most, as no other mainstream language does this.

the only problem is ambiguous syntax
In other languages with such facility this problem is solved with some specific keyword/operators preceding such lambda

May I suggest @a: (@b) => { b : b } ?
It's reminiscent of both C#'s lambda delegates and JavaScript's ES6 Harmony arrow notation for functions with preserved lexical scope. Provided you place detached rulesets at the head of the types the parser tries to apply for expressions (which I think it already is), this should result in a deterministic parsing without ambiguities.

And if you _really_ wanted to have a list with an explicit => symbol in there, well; there's always ~"=>" as an escape hatch. (It's not like you don't already have this problem with calc() to some degree.)

rather than something that was thought about up front.

(Though we're quickly going offtopic - Yes, it was not something thought of the earlier Less days but today it still remains _the only_ way to "return" something from something in Less (since the "function" facility is still somewhat neglected, though actually the whole thing is not limited to returning variables). So I insist :) that "parent scope leak" has nothing to do with lambdas, after all lambdas do not return anything at all. And the rest is another unrelated story).


@a: (@b) => { b : b } - Yes, could be something like this (though personally I extremely don't like => :)

Per the discussion in #1943, and also related to passing mixins by reference (issue # ?), I would suggest this syntax:

.mixin(@args) {
    box-shadow+: @args black;
}
a {
    each(10px 10px, -20px -20px, .mixin);
}

...equal to...

.mixin(@args) {
    box-shadow+: @args black;
}
@list: 10px 10px, -20px -20px;
a {
    each(@list, .mixin);
}

...equal to...

@list: 10px 10px, -20px -20px;
a {
    each(@list, (@args) {
      box-shadow+: @args black;
    });
}

However, there's no reason that you shouldn't be able to use the same construct for user-defined mixins: .for(@list, .mixin); and do whatever you want with the list / mixin parameters.

To be accurate, this is an extension of mixins / parameters, and not detached rulesets, and passing mixins by reference has been discussed elsewhere (although I couldn't find it with a brief search).

and passing mixins by reference has been discussed elsewhere (although I couldn't find it with a brief search).

It was mentioned in https://github.com/less/less.js/issues/965#issuecomment-18462037 and then in more details discussed https://github.com/less/less.js/pull/1648#issuecomment-33859876 and following comments.

Per separate discussion with @plugin, this now seems like an ideal candidate to close in favor of a user-defined function via @plugin.

Also, this is strangely relevant to #1505 which I just commented on, because we're demonstrating passing a CSS list to a custom function.

this now seems like an ideal candidate to close in favor of a user-defined function via @plugin

Note that currently you can neither pass identifiers like .mixin to a function (not allowed by the parser) nor you can call a function w/o assigning its result to something... So, no, for now it's simply impossible to implement such each via plugin.

So, no, for now it's simply impossible to implement such each via plugin.

Oh. Damn. Welll.... wouldn't it seem like a good candidate for adding more flexibility for plugins? What I mean is, it's a narrow use case, and I think we had talked about narrow use cases as candidates for user-defined plugins. OR optional core plugins (which isn't defined yet).

So, to me, if this went in, it's either expanding core in one way or another. One would be specific to this use case, and one would be expanding plugins to support scenarios like this use case.

@seven-phases-max
So, no, for now it's simply impossible to implement such each via plugin.

Oh... I wouldn't be too sure about that.

Oh... I wouldn't be too sure about that.

Yes, but in your example, it seems you're only doing a partial solution with the plugin, and then doing the other part in a recursive mixin. My question was basically if a plugin could do this (from one of my examples above:

@list: 10px 10px, -20px -20px;
a {
   @plugin "awesome-list-functions-which-contains-each";
    each(@list, (@args) {
      box-shadow+: @args black;
    });
}

Based on your gist, it looks like you're agreeing with @seven-phases-max. No?

it seems you're only doing a partial solution with the plugin

I could just as well put the loop construct into the actual function. I chose not to, to illustrate that you can infact have full lambda delegate functionaity just by binding variables into the detached ruleset scope.

Infact; check the same gist again. I just updated it with named variables support...

Based on your gist, it looks like you're agreeing with @seven-phases-max. No?

The argument was that you could neither pass identifiers like a mixin to a function (so you could pass parameters to the mixin serving as a lambda delegate) nor execute a function without assigning its output to a variable or property (preventing you from using it to make a lambda that can emit CSS).

In my case, I use the function to wrap a second detached ruleset around the original, which injects the lambda's 'parameters' (very similar to how Definition.prototype.evalCall works when passing parameters into mixins, actually) and then I return that wrapped ruleset as a result.
A detached ruleset, ofcourse _can_ be called to emit CSS.

This wrapping layer solves both problems.

@rjgotten

Oh... I wouldn't be too sure about that.

What you show there is a mixin that is not different from for example this (there you just simplify Less loop via some lambda function) but to be fair the plugin is sort of redundant there as we can write a mixin with the identical functionality without any plugin (if I'm not mistaken the plugin there is used only to ban outer scope variables).

So, no, to cancel:

So, no, for now it's simply impossible to implement such each via plugin.

You need to show each as a function not as a mixin ;) (otherwise it's just the same discussion as after https://github.com/less/less.js/issues/1943#issuecomment-72945408).

The argument was that you could neither pass identifiers like a mixin to a function

And you still can't. DR is not a mixin! :) (it can't have parameters for example). E.g. as a user I want my code to have lambda args named @v and @i (or whatever I need) I don't want that predefined @item and @index bloatware! ;)

as a user I want my code to have lambda args named @v and @i (or whatever I need) I don't want that predefined @item and @index bloatware! ;)

Did you miss the part where the parameter names are custom bindable? My example has them fixed, but you _could_ have them passed in as an additional argument to whatever mixin is going to process the lambda ruleset. Using my gist as an example:

.each(@list, @params, @ruleset) {
  @length : length(@list);
  .iterate(1);
  .iterate(@index) when (@index =< length) {
    @item : extract(@list, @index);
    @out  : lambda(@item, @index, @params, @ruleset);
    @out();
    .iterate(@index+1);
  }
})

.each(foo bar baz, v i, {
  value-@{i} : @v;
});

That's not very DRY though and it puts burden on mixins offering callback semantics to set this kind of parameter configuration up. A better way would be to split the lambda function itself out into two components:

  1. a bind(@name-a, @name-b, ..., @ruleset), and
  2. a pass(@bound-ruleset, @value-a, @value-b, ... )

At that point a caller has complete control over parameter names:

.each(foo bar baz, bind(v, i, {
  value-@{i} : @v;
});

and internally the mixin would not have to worry about them, and simply call pass and then evaluate the resulting wrapped detached ruleset.

.each(@list, @ruleset) {
  @length : length(@list);
  .iterate(1);
  .iterate(@index) when (@index =< length) {
    @item : extract(@list, @index);
    @out  : pass(@ruleset, @item, @index);
    @out();
    .iterate(@index+1);
  }
})

Sure; it's still not a _real_ native lambda, but it gets _awfully_ close, doesn't it? ;-)


Ofcourse, it's all still a hack that would hopefully be replaced with native language support at some point in time. But until then it's a nice semantically (and almost syntactically) compatible 'polyfill'.

.each(foo bar baz, bind(v, i, { ...

In that context the three-parens-overkill is capable of more magical things:

.each(foo bar baz, { .function(@v, @i, @l) {
    threesome: @v at @i of ~"[" @l ~"]";
}});

(impl.).

@seven-phases-max
That's actually what I had been using before plugin functions and as you already noted in your gist: it breaks on overloaded signatures.

You _can_ make it work in the simple case with a 'catch-all' signature based on a rest parameter (so that the .functioncall will always have a matching signature something to match, regardless of the parameters the consumer has supplied).
However, it then starts doing really, _reaaaaa------lly_ weird stuff when you start using nested call structures such as an .each inside an .each. Using the detached ruleset directly sidesteps that issue.

Anyway, I think I just had an epiphany on setting this up with some nicer syntax. I'm going to hack on it a bit more and see if it will work. ^_^

However, it then starts doing really, reaaaaa------lly weird stuff when you start using nested call structures such as an .each inside an .each . Using the detached ruleset directly sidesteps that issue.

Yep, that's why I stick to my DR-free .for after DRs were introduced... It has no problems with nesting, allows custom @item name and the only problem there is to watch it's not used in a mixin to be expanded into the global scope w/o surrounding {}.

So; some news. I kind of figured out how to get a reduce operation to work:

.test {
  @list : 1 2 3;
  @init : 0;
  @step : { @return : @previous-item + @item; }

  .reduce(@list, @init, @step, {
    value : @result;
  });
}
.test {
  value: 6;
}

(Yes; I do have a @plugin function that pulls assignable return values out of evaluated rulesets...)

(Yes; I do have a @plugin function that pulls assignable return values out of evaluated rulesets...)

Just to share an idea. I have some plugin in development (but have not time to finish it soon I'm afraid) that allows you to use native Less mixins as native functions in the canonical way, e.g.:

.function-foo(@x) {
    return: @x * 5; 
}

usage {
    width: foo(100px); // -> 500px
}

Obviously this requires a few dirty hacks from within the plugin (mostly related to getting the proper scope context for the called mixin inside its function-wrapper) since that's not really something plugins are supposed to do at all.

But the point is: probably you could consider something in this direction too? (to get rid of too deep DR dependencies/hacks). As I see now the method you use to make all these things is quite curvy, i.e.:

  • make a mixin to accept DR as lambda.
  • make a helper function(s) to hack DRs to make it help to make what the mixin has to make.
  • invent some hacky way to use that mixins if you're not happy with default variable names (or for any other reason the passing-DRs-in syntax does not fit well)
  • etc.

But the point is: probably you could consider something in this direction too? (to get rid of too deep DR dependencies/hacks).

It's actually surprisingly clean, moreso now that I've evicted some more of the cruft. ;-)

Anyway, wouldn't mixins give you a few problems when used as lambdas? One I can think of off the bat is that a single mixin call may match multiple definitions and thus can result in multiple return values. In which case; which one do you take?

In which case; which one do you take?

Just the same way as anywhere else: LDW.

Ah, and again just in case: https://github.com/seven-phases-max/less-plugin-lists (I'm doing last minute make-ups before publishing). This thing is strictly orthogonal to what you're crafting (primitive functions vs. iterator algorithms), but it would be curious to see how both will affect various use-cases (key-value look-ups in particular).

I actually have primitive functions as well; both for lists and maps.
My list augments are pretty much what you've written as well.

I saw again that someone wanted a kind of native each to pass a set of data to a mixin - #2601.

I think there's still room for the possibility of something native.

Today, I did a simple mixin to output rems when it's supported by a browser:

.rem-size(@property, @size) {
    @{property}: @size;
    @{property}: unit(@size / 10, rem);
}

Add in my elements:

.widget {
  .rem-size(font-size, 14px);
  .rem-size(width, 50px);
  .rem-size(height, 50px);
  .rem-size(padding-top, 10px);
}

I realized after a moment that was unfortunately a little sloppy, and not easily readable. What I wanted was something more like:

.widget {
  .rem-size({
    font-size: 14px;
    width: 50px;
    height: 50px;
    padding-top: 10px;
  });
}

It instantly feels more self-documenting to me as far as what property values I'm transforming. Of course... there's nothing like this in Less. What I wanted is something that would expand all property / value pairs into mixin calls for each one.

If you look at the number of use cases / issues related to this, I think we're missing the boat. Expanding lists into mixins is an oft-requested feature; it's just that people ask for it in a bunch of different ways.

Plugins can maybe do this, but I'm not sure now that they necessarily should. I think we can agree so far in this thread that the functionality is needed, and we just need to decide

  • [ ] if we should add to core,
  • [ ] what the syntax should be.

Is that a fair statement?

@matthew-dean pixrem? Writing a postprocessor plugin to inject fallbacks for a normally written CSS like font-size: 1rem; sounds more reasonable (beside artificial syntax, lambda/each/whatever construction would also have problems with properties like background: linear-gradient(135deg, red, blue) 1rem/2rem repeat-y fixed 3rem 4rem;).

Abstract "ruleset to property/value pairs array" (and back) conversion function/construction sounds fine, but then it's about use-cases to shape its actual syntax/behaviour. So hypothetically, not counting background: linear-gradient(135deg, red, blue) 1rem/2rem repeat-y fixed 3rem 4rem;-like things, but do counting stuff like padding: 1 2 3 4, how .rem-size would look internally?

@seven-phases-max :
Abstract "ruleset to property/value pairs array" (and back) conversion function/construction sounds fine

Heh. I'm actually using a plugged-in set of functions to build dictionaries that way.

Works great for stuff like mapping series of font-icons with auto-incrementing character code ranges, since you can trigger evaluation of the ruleset (and any mixins inside it; thus building loops) before the evaluated result gets turned into a dictionary of key-value pairs.

e.g.

@map-icons     : map-build(@ruleset-icons);
@ruleset-icons : {
  @chevrons : "chevron-up", "chevron-right", "chevron-down", "chevron-left";
  @arrows   : "arrow-up"  , "arrow-right"  , "arrow-down"  , "arrow-left";

  .make-iconrange(@chevrons, "e800");
  .make-iconrange(@arrows  , "e810");
}

.make-iconrange(@names, @code) {
  .iterate(hex2dec(@code), length(@names));
  .iterate(@start, @index) when (@index > 1) { .iterate(@start, @index - 1); }
  .iterate(@start, @index) when (@index > 0) {
    @name   : e(extract(@names, @index));
    @{name} : dec2hex(@start + @index - 1);
  }
}

// [ ... ]

.icon:before {
  @code   : map-extract("chevron-up");
  content : "\@{code}";
}

(Internally, it uses a completely new node type to store the key-value mapping as an object literal, giving you constant-time lookup instead of having to walk the entire list all the time to extract a value for a known key. The speed-up is quite dramatic.)

@rjgotten

Heh. I'm actually using a plugged-in set of functions to build dictionaries that way.


For this particular case all those DRs, corresponding .map- mixins and loops there look quite overengineered for me. Notice that all that code is used only to emulate C-like enums, e.g.:

enum {
    a,
    b = 33,
    c,
    d = 42,
    e,
    etc
};


It's not even quite a "map/dictionary". If I would try to abstract such code via external plugin functions I'd use something similar to less-plugin-lists:at (with just slightly different behaviour), e.g. (not showing hex<->dec conversion):

@icons:
    chevron-up #00e800, chevron-right, chevron-down, chevron-left,
    arrow-up   #00e800, arrow-right, arrow-down, arrow-left;

.icon(@name) {
    @code: enum-value(chevron-down);
    content : "\@{code}";
}

.icon:before {
    .icon(chevron-down);
}


I.e. just one custom function, no loops and all the data is collected in same place (no need to repeat all those @icon-family variables). And if you prefer such function to work with DR (instead of simple list) it could be similar:

@icons: {
    chevron-up: #00e800; chevron-right; chevron-down; chevron-left;
    arrow-up:   #00e800; arrow-right; arrow-down; arrow-left;
};

(but since it's illegal to define a property/variable w/o a value this way it becomes quite more tricky, though in a simple case a bit of artifical ,/; mix-up should help).
(And btw., why do you use all these quotes for values? CSS is not JavaScript! :P The only required " in your snippet are in content value.)


This does not cancel the feature usefulness for other use-cases of course.

For this particular case all those DRs, corresponding .map- mixins and loops there look quite overengineered for me.

True. I should've said I'm _currently_ using a plugin function to build dictionaries that way. My plan is to rewrite the hex/dec conversion for the icons to a dedicated function at one point and abstract away the last bit of messiness with that native Less loop. Just haven't had the time to do so yet.

One thing though:
the fact that you can make a _real_ map/dictionary that works on a JS object literal 'hash-map' internally really does have rather great practical value if you have to work with sets in the hundreds of items: looping through a list-of-list via native Less becomes quite slow in that case.

(My example wasn't quite representative of that fact. In truth I'm dealing with sets of 100+ icons in custom-authored icon-fonts and I quickly found out that a native Less loop for lookups doesn't cut it.)

@seven-phases-max I feel like you might have got hung up on the use case / syntax, when all I was saying is that I wanted a clean way to write a list that I could iterate through.

My basic point was: a lot of people come across scenarios with Less where they
a) have a list,
b) want to execute a mixin for each member of the list.

Whether or not my particular scenario can / should be done with a postprocessor is somewhat irrelevant and a matter of opinion.

(Although two opinions about that:
1) Less provides compiled CSS to PostCSS which has to then be converted back to an AST, so PostCSS-based processing is a rather inefficient pattern if a mixin solves it,
2) Even if it solves it, it adds a tech burden on my processing stack. But, again, I'm not really interested in the merits of PostCSS as far as this thread.)

While the issue at top is asking about lambdas specifically, most of the discussion has been around "each"-type syntax, because the original REASON @rjgotten wanted lambdas or "parameterized mixins as detached rulesets to form lambdas is in the initial example: .forEach.

So I was simply revisiting this: this being the question of a native each(). And posing the question again on what exactly is our consensus, not is my use case valid.

So to state those questions little more clearly:
1) Is there a need to send lists to mixins that can operate on each member of the list? (I think so, because it's part of our regular documentation.)
2) Is there enough need that a native feature is still a consideration? Rather than resorting to .-() when hacks?
3) If there's a need, what might be the syntax?

I'm just curious where this stands and what the opinions on the table are.

@matthew-dean

b) So I was simply revisiting this: this being the question of a native each().

But you already did this at https://github.com/less/less.js/issues/2270#issuecomment-72969605.

So since the large part of your post was about pix/rem example I only added a remark that such polyfill is barely a subject for each or lambda features at all because of its specifics, a remark with the only purpose of preventing a bit akward use-case to negatively affect further syntax/behaviour decisions. If you try to use a list and a loop for the rem/px polyfill you _will_ end with code like this (loop syntax does not really matter there, the problem is in loop internals). So that's why I suggested:

Writing a postprocessor plugin to inject fallbacks for a normally written CSS like font-size: 1rem; sounds more reasonable


A "postprocessor plugin" does not mean "a wrapper for an external tool/library", it just means "a plugin that transforms Less code after it's evaluated". E.g. such "rem polyfill" can be (and is better to be) written as "post-evaluation visitor" operating directly with Less AST (thus having nothing to do with PostCSS or other external stuff at all).


Regardless of that rem polyfill remark, I see nothing wrong in a native each (working with both plain lists and with rulesets as key/value arrays). Though notice that the more @rjgotten polishes his library the more he moves in a different direction: no loops and direct dictionary look-up (which Less itself can't use with its rulesets unless they contain _only_ variables). So we're starting to have two quite orthogonal features here.
(Actually even three: generic lambda (obviously not necessarily related to loops), each and a key/value structures as a dictionary (for direct look-up) or as an array (for loops)).

But you already did this

Sure, ok, maybe my comment was too long and I should have just said, "This popped into my brain again today; where are we at with this?" As far as I could tell, we hadn't come close to any consensus. Sorry if that was confusing.

Part of the discussion relevant to that comment is the question of passing selectors by reference. @seven-phases-max you actually mentioned in #1848 that that discussion of ${} referencing was relevant to this topic.

So, it could very well be:

each(a b c; ${.mixin} );

or

each(a b c; ${#namespace.mixin} );

Maybe #1848 & #2433 needs to be implemented first?

Yes, I suppose these things will be useful together (and thus should take each other into account when designed), but there's no direct dependency. E.g. each(a b c; ${.mixin} ); is cool (for complex stuff) but it does provide mechanismus for easy to use inline loop body.

You mean, just use a lambda instead?

Oops. It's just a typo. I mean "but it does _not_ provide mechanismus for easy to use inline loop body".
In other words, ideally it still should be a way to write all necessary code inside each itself, like in:

each(@list, (@value) {
    box-shadow+: @value black;
});

or (just trying to get out of JS-like syntax):

each(@list): (@value) {
    box-shadow+: @value black;
}
// ^ this one won't be compatible with external DR or mixins of course :(

or so...

Yes, if we allowed anonymous mixins, then I think they would be interchangeable with whatever we use as a mixin reference. So, yeah, I think your first example is legitimate and compatible.

Your assignment example is interesting. I like the idea of something cleaner.

I've also wondered if there's a way to flip it, to sort of circumvent the mixin reference issue. That is, some way to pass the list to the mixin, rather than pass the mixin and list to an each, like:

.mixin[@list];  // Different syntax to evaluate each member of the list?
// or
.mixin each @list;   //better

Then it's still a little cleaner to do even with lambdas:

(@value) {
    box-shadow+: @value black;
} each @list;

// or maybe better as:

(@index, @value) each @list {
    box-shadow+: @value black;
} 

And potentially:

.mixin each @list when (length(@list) = 1);

What do you think of that?

Either way I think you hit it right that my initial proposal is too JavaScript-y. I like this more "declarative expansion" idea.

Although, probably this needs one slight syntax adjustment, to allow for passing lists as lists rather than vars:

@list: a b c;
.mixin each (@list);
.mixin each (d e f);

//with mixin definition
.mixin(@letter) {
  property: @letter;
}

Either way I think you hit it right that my initial proposal is too JavaScript-y.

It's not it's actually "JavaScript-y", sure it is just a natural continuation of the mixin call with DR argument syntax we already have: .mixin(arg1, {/* stuff */});, i.e. it's just parens and brackets of similar language entities shape together in a similar manner for a similar construction :)


Speaking of alt. syntax, indeed, your recent examples actually reminded me of one thought I always get to when dealing with complex lists/loops and related stuff. "It would be much easier and clean if functions and arithmetic ops could be launched in a 'vectorized' fashion". I.e. instead of writing bloated:

@x: 3rem;
padding: 1 * @x 2 * @x 3 * @x 4 * @x;

one could do just (pseudo syntax):

padding: (1 2 3 4) * @x;

and so on (including vector op vector -> vector things).

Same way, functions, (instead of trying to handle list args on their own where not necessary), could have "vectorized" call mode too, e.g.:

@base-colors: red green blue black;
@light-colors: <some vector call mark>:::lighten(@base-colors, 15%); // same unknown pseudo syntax

@another-list: 10 20 30 40;
@more-light-colors: <some vector call mark>:::lighten(@base-colors, @another-list * 1%);

Now, indeed, when you mentioned it, I realize that the same thing (no idea of syntax yet) when applied to mixins, is also nothing but just an each construction...
Curiously I suppose it would be relatively easy to implement, but the syntax itself might be a problem to craft (honestly I can't invent anything useful yet). Beside specifying that vector/each mark itself, it also ideally should provide control of what arg and arg_s_ a mixin/function call should be "vectorized" for.

E.g. given a .mixin(a1, a2, ..., an) {...} (or a function with multiple args), there're several variants to "each" it (assuming we don't want to put any restriction on the mixin itself and hardcode the construction itself for a particular cases only):

  1. call .mixin for each a1 item (a2 is passed as is)
  2. call .mixin for each a2 item (a1 is passed as is)
  3. call .mixin for each a1 and a2 items (as in .mixin(a1[i], a2[i]))
    etc. etc.

I.e. in other words, (for simplicity taking only explicitly defined mixins, w/o considering "inplace" iteration body yet), with a pseudo syntax it becomes something like:

each(<multiple args>) mixin(<normal-arg or a place-holder for one of "each" arg items>)...

// i.e.

@a: 1 2 3 4;
@b: 42;
@c: 5 6 7 8;

each(@a) mixin(<each-arg-1-place-holder>, @b, ...)
each(@a) mixin(@b, <each-arg-1-place-holder>, ...)
each(@a) mixin(<each-arg-1-place-holder>, <each-arg-1-place-holder>, ...)
each(@a, @c) mixin(<each-arg-1-place-holder>, <each-arg-2-place-holder>, ...)
each(@a, @c) mixin(<each-arg-2-place-holder>, <each-arg-1-place-holder>, ...)
// etc. etc.

Plus there're should be same place-holders for the current index of course (to handle conditional stuff inside). Etc.


Well, I guess you see the idea already.
(I realize all this may look extremely complicated since I'm trying to cover the whole story at once, but in general I believe this can beat whatever other iterating system it could be, provided one could invent a clean syntax with "you don't need to specify stuff you don't use there" approach where possible. Well, I'll keep thinking if I could (re)present this better).

E.g. given a .mixin(a1, a2, ..., an) {...} (or a function with multiple args), there're several variants to "each" it (assuming we don't want to put any restriction on the mixin itself and hardcode the construction itself for a particular cases only):

I think that, for simplicity sake, we would want to just support passing a single list to a single mixin at a time. I guess I did imply that if your list was a list of lists, then each inner list member would be passed as individual arguments. Theoretically, though, we could do both, via mixin arity matching.

.mixin(@list) { }
.mixin(@a; @b; @c) { }

@nested: 1 2 3, 4 5 6, 7 8 9;
.mixin each (@nested);   // or each(@nested)::mixin();  or whatever

If you define a mixin that matches one parameter, it calls any mixin matching one parameter and passes the list. If you have a matching mixin with the right number of arguments for the inner list, it could match that mixin.

I don't know if we have this as a guard (don't think so?), but you could do:

.mixin(@list) when (islist(@list)) { }

...in case you needed to separate cases of mixin called for list vs. called on list members.

Also, I think in the case of iteration, @index should show up as a magic variable (like @arguments), rather than needing to define it in the mixin definition itself (which is tedious). Such that you could do:

.mixin(@x) when (@index = 0) { 
  // add stuff for the first call
}

I like your vector stuff, but the syntax is probably a little too thin / ambiguous. (Parentheses already used with a regular math statement / mixin / function / etc) I also like the idea of a "directing" or "assignment" syntax from each to mixin. I too had tried to come up with something, or think of anything CSS-y but instead went with something LESS-y.

Good work!

I decided to try addressing this using existing syntax and structures: that is, passing a list and ruleset into a function and transforming the ruleset based on the members of the list.

Check out the related PRs.

@matthew-dean Mm... Here's nearly equivalent implementation (not optimized, w/o any error handling) _without_ requiring any changes in the core. The only difference is that instead of each it provides .each.

I.e. I hope you realize that since your implementation requires two quite major core changes, it's not going to be accepted any time soon. First we'll have to talk it over to the death. (Amount of things to analyze and amount of tests to write are a bit scaring).

P.S. Either way :+1:

Well plugins came first. I started trying to do something, anything useful in terms of using with a framework, and being only able to return a property value is of such limited value to be not that worth it.

But, if like mixins, functions can expand in place at any node depth, that opens up everything. Functions still really only return a single node, as they did before, but the restriction on node type is simply lifted.

The each() implementation was basically an afterthought after that. I realized that once you could define a function to expand into any node type, and that function could receive any other node type, well then the whole thing becomes very straightforward. I also wanted to test the existing API.

I know it's a big change. each() I don't care as much if its core, other than loop hacks tend to be somewhat common. But it could also be the showcased proof of concept for @plugin. (Maybe one of a few "core" plugins.) Either way has some validity.

As far as function changes themselves, if those can move forward, I would like to make further PRs that greatly simplify the API, and then I'd happily take on the responsibility of comprehensively documenting the API and updating less-docs. After working with the tree nodes, I think they're currently much too complicated and awkward to be worth documenting. But I have ideas on how to fix that.

But, if like mixins, functions can expand in place at any node depth, that opens up everything. Functions still really only return a single node, as they did before, but the restriction on node type is simply lifted.

The each() implementation was basically an afterthought after that.

:+1:

each() is a very nice _showcase_ of the widened applicability of functions in the grammar, but it's the widened grammar that's the true killer feature.

@rjgotten Right? I was playing with doing cool things like returning a detached rule set with a function to a variable and then calling it. So you could potentially do unique transforms on DRs (which is kind of what each() does), and you've basically got PostCSS-level power, except Less plugins play better with others by design, and are easier to install (because you don't have to install them at all before calling the less compiler).

I'd really love if the @plugin PR was reviewed by everyone, so that it needn't sit forever.

I'd really love if the @plugin PR was reviewed by everyone,

Do you mean #2785?
If so it's really _just_ "Root Functions" (or "Allow Non-Value Functions" as "Root" is confusing) feature as it's no way limited (or should be limited) to plugins of any format. I did a quick look at the code and did not see anything wrong. It's just the error handling to be polished (hmm, I guess I could make a PR to the branch for suggested changes)).

P.S. Can a function return an array of rules? (By now building & ruleset or DR-call emulation to return such array seems to be limited. What if I want to return a list of variable definitions? :) Though I suppose this could be improved later.

Yes, #2785. The reason @plugin was the subject is because I hadn't considered what it could mean for core functions. So, at the time I was just thinking of custom functions.

2786 was based on #2785, as otherwise you couldn't do a root function like each(). I didn't know a better git way to do it, but basically if #2785 was in, then #2786 would look a lot smaller. It's really just this commit: https://github.com/less/less.js/commit/f52b0736015d69f01e9be3257b039f2f332a613f

@seven-phases-max Hopefully you noticed that I made a test case of your "vector math" scenario, as a gift for you, lol. I'm happy to do other test cases or error cleanup for #2785, because I'm excited about this one.

By the way, just looked at your implementation. That's embarrassing that I didn't look at that earlier, or maybe promising that the implementations are so similar? I like that you used value rather than item. That's probably better. I didn't really like two vars that started with "I". Your implementation is great, but it still makes for a lot of excess code without root functions. Otherwise, our code is pretty darn close.

One reason I started looking at functions rather than a custom mixin is that, really, it's only logical that there's one each() function or mixin. With functions, that's true by design, so it's a logical match. And because your mixin calls a function which passes a value to a variable to a ruleset evaluation, all those steps are simply "moving" the function result to the root of the ruleset. The intended result is for your function to be evaluated at root.

or maybe promising that the implementations are so similar?

It's just because I literally stole the code from you :) (I just revised a few things so that the returned rules retain DR-like visibility).

I like that you used value rather than item. That's probably better.

In final version it also to be [value index] instead of [index value] of course.


In general, my implementation is just a demo workaround for "no root functions" problem (as well as no direct DR value passing), nothing more then that. Finally (in context of this thread) it's only exposing that the earlier:

nor you can call a function w/o assigning its result to something...

can be fooled after all (though other limitations still apply so that it's not yet _that_ each, but indeed it's pretty close).


But as soon as it goes wider than this particular thread, it's no doubt that "Allow Non-Value Functions" is the way to go. As well as "Improved Func Arguments Syntax" (to be honest I don't care of those ; there because I'll anyway stick to each(a b c, value index, {...});, but DRs must be passable in-place of course for the whole thing to work).

Oh haha I thought you were saying you had implemented it sometime before.

Yes the semi-colon provides the same optionality as what's used for Mixins, so it's no burden to support. In fact, I replaced the function arguments code with a modified version of the mixin arguments code, although I think I added "assignment".

Following up above "each() via plugin" stuff. I came to a new (plugin-based) implementation method that can be applied to virtually _any_ for/for-each/each syntax that can pass the parser.
In particular, by abusing #2824, the following syntax is currently possible (see the quick prototype):

@list: a b c, e f g;

usage {
    .for-each(@x in @list) {
        .for-each(@y in @x) { 
            r: @y of ~"[@{x}]";
        }
    }
}

Same way, virtually any other suitable syntax can be used instead, e.g.:

.for(@value, in, @list)  {...} // mixin def. not abusing #2824
.for(@i: 0, @n: 6)       {...}
.for(any thing)          {...} // not abusing #2824 as long as no @
@for (any thing)         {...}
.for(@value in @list,    {...});
:for(i 1:6)              {...}
:for(value in list)      {...}
 for [value] in [list]   {...}
 for {@value: in @list; -{...}}
// etc. and so on

(A complexity of evaluation code may vary a lot though).

@seven-phases-max
That is equal parts awesome and horrifying...

@rjgotten @seven-phases-max Agreed. Although maybe 60% horrifying.

Found this fascinating thread when I found myself somehow feeling like I needed closures in CSS 😄

Right now I have this mixin for generating responsive prefixed versions of different utilities:

.responsive(@ruleset) {
    & { @prefix: ~""; @ruleset(); }
    @media (min-width: @screen-sm) { @prefix: ~"sm-"; @ruleset(); }
    @media (min-width: @screen-md) { @prefix: ~"md-"; @ruleset(); }
    @media (min-width: @screen-lg) { @prefix: ~"lg-"; @ruleset(); }
    @media (min-width: @screen-xl) { @prefix: ~"xl-"; @ruleset(); }
}

Used like:

.responsive({
    .@{prefix}block { display: block;}
    .@{prefix}inline-block { display: inline-block;}
    .@{prefix}hidden { display: none;}
});

Works fine, but I wish that @prefix variable could somehow be a parameter to the ruleset, instead of having to define it immediately before invoking the ruleset and making the ruleset aware of the name.

I'd love to be able to rewrite all of the above something like this:

.responsive(@ruleset) {
    & { @ruleset(~""); }
    @media (min-width: @screen-sm) { @ruleset(~"sm-"); }
    @media (min-width: @screen-md) { @ruleset(~"md-"); }
    @media (min-width: @screen-lg) { @ruleset(~"lg-"); }
    @media (min-width: @screen-xl) { @ruleset(~"xl-"); }
}

.responsive((@prefix) {
    .@{prefix}block { display: block;}
    .@{prefix}inline-block { display: inline-block;}
    .@{prefix}hidden { display: none;}
});

Is that the sort of thing the ideas presented in this thread might make possible?

Btw. I've occasionally got the idea of possible ambiguity syntax resolution (to answer https://github.com/less/less.js/issues/2270#issuecomment-61634539). We could just put some symbol right before the parameter list, e.g.:

@var: @(parameters...) {...body...};
.call(@(parameters...) {...body...});

(I was recently writing some Matlab code and - boom - realized that they use just that for anonymous functions). Obviously it doesn't have to be the @ symbol (could be ., ? etc., but not arithmetic or relational operators), and then it's just the *( "token" that unambiguously designates the beginning of the whole construction (I believe it will be much easier and btw. faster for the parser and eyes. And yep, I don't like the JS => syntax).

Btw., @adamwathan you know that your current code will fail if you occasionally have a global @prefix variable, do you?

Hey @seven-phases-max!

Btw., @adamwathan you know that your current code will fail if you occasionally have a global @prefix variable, do you?

Totally, this is a big reason why I wish I could define a detached ruleset that accepted parameters, so any data it needs can be properly scoped.

Can you think of an alternative way to write and use this generator mixin that wouldn't have that issue?

.responsive(@ruleset) {
    & { @prefix: ; @ruleset(); }
    @media (min-width: @screen-sm) { @prefix: sm-; @ruleset(); }
    @media (min-width: @screen-md) { @prefix: md-; @ruleset(); }
    @media (min-width: @screen-lg) { @prefix: lg-; @ruleset(); }
    @media (min-width: @screen-xl) { @prefix: xl-; @ruleset(); }
}

.responsive({
    .@{prefix}block { display: block;}
});

alternative way

It depends on how you use that "parameter". For instance in a simple case like above you don't need any parameter at all [1]:

.responsive(@ruleset) {
    @ruleset();
    @media (min-width: sm) {sm-{@ruleset();}}
    @media (min-width: md) {md-{@ruleset();}}
}

.responsive({
    &block {display: block}
});

The extremes of the other side would be:
[2] "the triple parens" method, e.g.:

.responsive(@rules) {
    & {@rules(); .-(~'')}
    @media (min-width: sm) {@rules(); .-(sm-)}
    @media (min-width: md) {@rules(); .-(md-)}
}

.responsive({.-(@prefix) {
    @{prefix}block {display: block}
}});

and "up-side-down" one, e.g. [3]:

.apply-responsive() {
    .responsive(~'');
    @media (min-width: sm) {.responsive(sm-)}
    @media (min-width: md) {.responsive(md-)}
} .responsive(...) {} .apply-responsive();

.responsive(@prefix) {
    @{prefix}block {display: block}
}

And a lot of variations in between these three above depending on the actual use / acceptable impl.-vs-usage verbosity bounds (e.g. for the usage verbosity the [3] method wins even over parametric DRs, but it's limited to global .resposive definitions only, thus any nested styles have to be moved there).

@seven-phases-max that's all super helpful, thank you! You're right, the first option works perfectly in my case, hadn't thought to use & to handle this. Thanks!

Obviously it doesn't have to be the @ symbol (could be ., ? etc., but not arithmetic or relational operators), and then it's just the *( "token" that unambiguously designates the beginning of the whole construction (I believe it will be much easier and btw. faster for the parser and eyes

@( is a fairly fast exit for the parser, since it only needs one character past @ to determine a parametric DR, but I get the hesitation with using @ for another thing. Of the symbols above the top row of the keyboard, it's probably down to !(, @(, $(, ^(, *(, =(. I think $ and ^ are non-starters, and =( looks kind of sad, so you're left with !(, @(, *(.

My only hesitation with * is that it at first looks like the start of a mathematical operation (albeit an impossible one), but I don't feel strongly about it.

Other than that, I think your instincts are good, and a single token designator for parametric DRs is smart. I like this idea and it gels well with separate efforts to align mixin / DR behavior. 👍

Thinking of it further I guess I like .( more because it does look like exactly like "mixin w/o name" :) Though I realize it's probably better to be something more noticeable.

Posting this just for the sake of interested parties to be able to try and find various pros and cons of the decisions (sometimes quite radical) made in the plugin before deciding on details of the corresponding in-the-core features

So meanwhile the production version of the earlier mentioned plugin-based .for and .for-each statements are available in less-plugin-lists. See the documentation (don't the miss the advanced part) and included tests (1, 2).

Remark: these are really statements, i.e. they do not cover use-cases of a callback-oriented for-each function (which is to be implemented as ... well, as a function when we actually have parametric callbacks or a sort-of).

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

I came across this Bootstrap v4 Less port, and this is someone who is obviously using Less a great deal, and the lack of for/each syntax in Less raised on the opening page: https://github.com/seanCodes/bootstrap-less-port

Which is to say it's still an issue. I think there are a few starter threads here that offer possibilities, but I wonder if we can't do something that gels well with inline rulesets, detached rulesets, and mixins. This is based on some comments about @seven-phases-max about the downsides of "passing" the iterator in a function-like syntax. Instead it should just come at the end, either as a plain ruleset, or detached ruleset var, or mixin name.

As in:

[each syntax] [local vars] [ruleset|dr|mixin]

So, for example:

.foo {
  :each(@key, @value in @list) {
    @{key}: @value;
  }
}

// or

@dr: {
  @{key}: @value;
};
.foo {
  :each(@key, @value in @list) @dr;
}

// or
.mixin(@key, @value) {
  @{key}: @value;
}
.foo {
  :each(@key, @value in @list) .mixin;  
  // or   :each(@key, @value in @list) > .mixin;  ?
}

You could also, like @arguments, inject @key, @value if only @list is provided. As in:

.foo {
  :each(@list) .mixin
}

I'm not sure if :each is the correct syntax, but a plain each() doesn't make sense because it's not function syntax, and I think we agree @each() is already a non-starter for Less, which uses @ for at-rules and vars and variable calls already.

:each() would make a "special behavior selector" like :extend(), which to me makes sense. Thoughts welcome.

I wonder if both :extend() and :each() shouldn't be ::extend() and ::each() to mimic pseudo-selectors ::before and ::after. I dunno, just spitballin' syntax. Based on this thread, I think this is the best "construction" in terms of grammar, but not sure of the syntax of how to prepend each specifically.

🤔 On second thought, :: is unnecessary. Keeping the syntax lean is better.

🤔 On second thought, :: is unnecessary. Keeping the syntax lean is better.

Agree. I also think it's more in-line with pseudo-classes that accept params like :not() and :nth-child(), and I think :: is only used for pseudo-elements, anyway (ie. pseudo-selectors that change the actual target of the selector rather than altering the list of matches, like ::before or ::placeholder).

As for :each() itself, I'm really liking this proposal. Very "Less-y". Mixins are definitely an issue, but I wonder if we call that out-of-scope for initial implementation. I think the need is great enough that a DR-only solution would ease the pressure on libraries like bootstrap-less-port.

I'll say, like :extend(), it should be a pseudo-selector for real, meaning we should require &:each() instead of just :each() if it's nested.

.foo:each(@list as @i, @item) {
  &-@{i} { item: @item; }
}
// OR
.foo {
  &:each(@list as @i, @item) {
    &-@{i} { item: @item; }
  }
}

Another option is an at-rule, which also already have the [name] (params) {ruleset} structure in CSS. I admit feels just a bit "Sass-y", but it looks really clean.

@each (@list as @i, @item) {
  .foo-@{i} { item: @item; }
}

Though, actually, the same cleanliness can should be possible with &, as long as :each() allows a null "base selector".

&:each(@list as @i, @item) {
  .foo-@{i} { item: @item; }
}

Does that complicate how folks think about &:extend(), since that one _can't_ have a null base selector?

SIDEBAR: This is a thought for another thread and another day (if it has any merit at all) but I know the hesitation with more at-rules is future-proofing. One solution for that could be to add aliases for Less-specific at-rules that have -less- as a "vendor" prefix (e.g @plugin has @-less-plugin as an alias, @each has @-less-each, etc.). It'd be an option, probably (¿maybe --prefix-at-rules/-@, maybe taking a list of at-rules to prefix?) and I know how everyone _loves_ adding options. :P


Funnily enough, @seanCodes, the bootstrap-less-port guy, is my brother. I'll try to get him here to add his 2¢.

I'll say, like :extend(), it should be a pseudo-selector for real, meaning we should require &:each() instead of just :each() if it's nested.

_Funnily enough_, I was inclined to recommend (separately) that :extend shouldn't need &:extend. I mean, it's meaning isn't ambiguous without &.

But in regards to &:each, you have to consider that you might want loops of lists to _create selectors_ in the first place. But.... Less doesn't mind & at the root. 🤔

Another option is an at-rule, which also already have the [name] (params) {ruleset} structure in CSS. I admit feels just a bit "Sass-y", but it looks really clean.

Yeah, you're not wrong. And maybe @each is semantically more similar to @media. I can't really decide, because there's no real CSS equivalent in the language. It's probably more similar to functions in that it's an evaluation directive. It's just that functions are awkward for this use case of assigning to a ruleset.

The concept isn't hard here; it's really about the syntax. And maybe we don't need to be allergic to Sass syntax. I'm just wondering what the "declarative expansion" should be.

Maybe &:each is, in fact, not bad, because you're essentially merging the results into the current scope, selector or otherwise.

Funnily enough, @seanCodes, the bootstrap-less-port guy, is my brother. I'll try to get him here to add his 2¢.

Ha! Small world! Then why isn't he on the Less team too? lol

@calvinjuarez

One of my fave syntax suggestions in this thread is from @seven-phases-max, where he makes it function-like, but with an assignment to a "lambda", like this:

each(@list): (@value) {
    box-shadow+: @value black;
}

The thing that's nice about it is that it's just very plain. It's function-like, and assigns to something that looks like an anonymous mixin. You're probably right in this comment: "Mixins are definitely an issue, but I wonder if we call that out-of-scope for initial implementation. I think the need is great enough that a DR-only solution would ease the pressure on libraries like bootstrap-less-port."

After all, you could just do:

each(@list): (@value) {
    .my-mixin(@value);
}

And then your inner mixin could have when guards or whatever for values passed to it. Depending on the nature of your list, it could then be:

each(@list): (@key, @value) {
   // Obvs can also just put your rules in here too
    .my-mixin(@key, @value);
}

I wonder if @seven-phases-max's syntax isn't better than &:each or :each, just because it's not necessarily "related to a selector". It's really more of a functional directive, and not really like :extend at all. It also doesn't add in or as as keywords to the syntax.

I mean, it's been a few years and this syntax still sticks out for me. And it's more conducive to constructions like:

.rules(@val) when (@val=warning) { ... }
.rules(@val) when (default()) { ... }

each(@selectors): (@value) {
  .badge-@{value} {
    .rules(@value);
  }
}

The only keyword I might add is something like the to keyword for this:

each(1 to 10): (@i) {
  @val: extract(@list, @i);
  .mixin(@val);
}

And then of course make 1 or 10 just values, so you could do:

@start: 1;
each(@start to length(@list)): (@i) {
  @val: extract(@list, @i);
  .mixin(@val);
}

Oh, one more thing, if this is just an "anonymous mixin", you should be able to do something like:

each(@colors): (@color) when (hue(@color) < 60) {
  background+: @color;
}

That is, mixins can have guards, that's all. It's not a big deal, but it should just be called out for consistency.

One of my fave syntax suggestions

Hmm, I'm not sure I'm in love with that syntax. It feels very foreign to Less and CSS, and actually kind of all-around strange. It's almost, but not quite a function. It seems like just another concept to get people to learn.

It's really more of a functional directive...

My main concern with that is that (AFAIK) CSS never uses functions except inside property values. Root-level directives are always at-rules.

It also doesn't add in or as as keywords to the syntax.

It looks like it's just using : instead of a keyword. Which is fine. Maybe harder to read.

&:each(@list:@i) {
  /* rules */
}
@each (@list:@i) {
  /* rules */
}

Funnily enough, I was inclined to recommend (separately) that :extend shouldn't need &:extend. I mean, it's meaning isn't ambiguous without &.

I like the & in &:extend. It keeps it clear that :extend() is a pseudo-class. Extension requires 2 rules (or selectors, I guess), the current one and the one to extend. This syntax ties :extend to selectors in a nice way. I'm not sure 1 character is worth the trouble and conceptual twisting.

The only keyword I might add ...

I like to.

As a sidetone, this is starting to feel like more of a for and less of an each. ("Each" just seems to refer to each of a set, i.e. list/array, whereas "For" feels like it's referring to more arbitrary indexes.) I think "for" is a more flexible word, from a semantic point of view.

Really, though, since this is all about syntax, I think the best thing to do would be to borrow from languages Less users are likely to know.

@for (@i in @list) {
  @item: extract(@i, @list);
  /*stuff*/
}

Another, more declarative word is loop.

@loop @list(@i, @item) {
  /* stuff */
}

Thinking more about :each(), I don't think it needs &. :extend() _has_ to have a thing that it makes an extension of its param. It doesn't make sense as a "selector" on its own. But that's not true of _most_ css pseudo classes. :nth-child(), :not(), :lang(), etc., _all_ can stand alone and have meaning. No reason :each() can't. In fact, :extend() is kinda _weird_ that way.

:each(@list @i) { // no reason this should error
  /* stuff */
}

Which means the core question is: @, :, or a function/near-function.

My main concern with that is that (AFAIK) CSS never uses functions except inside property values. Root-level directives are always at-rules.

True, but as of Less 2.7, there are no restrictions on where functions can be called. But I get your point.

I'm not against for although @for..... 🤔 I mean a lot of languages use forEach or for-each. And yeah, I think that's all about where you feel Less sits in the language space. Adding to the language is always a hard decision.

I guess I'm trying to figure out what's parallel to what @cloudhead set out with the mixin guard syntax, which was a very deliberate choice vs. if. That is, it was a "evaluate this under these conditions". It was a set of _conditions_ for evaluation. Which if could be viewed that way also, I just want to be faithful to the intent of the language. It's a big change, no matter how obvious the need.

To me, :each(@list @i) {} or :each(@list: @i) { doesn't properly communicate the iterative nature of the evaluation. It seems like a one time evaluation i.e. @i equals the full value of @list. Whereas something like :each(@list): (@i) { } makes it much clearer that there's an assignment happening to a mixin-like construct.

I'm not against :each() vs. plain each() for the first part. I get the concerns there.

What about....

:each(@list) > (@val) { } 

Is > less abusive to the language than :? You're right it probably doesn't belong there. I guess I just liked the "semantic feeling" of the syntax construction.

--
Maybe :for(@val in @list) { } is even cleaner? You're maybe right that it works better than starting with the list?

@seven-phases-max Had :for (@val in @list) in this comment along with other for variants.

@calvinjuarez Do you prefer something like:

:for (@val in @list) {}
:for (@key, @val in @list) {}
:for (@i in 1 to 10) {}

I'm curious what @seanCodes has to say of what would have felt most logical and also "Less-like" when porting Bootstrap.

I just want to be faithful to the intent of the language. It's a big change, no matter how obvious the need.

This is a good point. Whatever we do can't really be "undone". I think at this point we probably need to get more input.

This is a good point. Whatever we do can't really be "undone". I think at this point we probably need to get more input.

You are correct. But also this issue has been open for.... [scrolls back]....four years. I mean, at some point you do your best and make a decision. I do like reaching something close to consensus though.

But yes, there's no hurry, let's see if there's renewed interest in coming to a consensus.

Do you prefer something like:...

Yeah, I think all those are really clean. Though that has the keyword problem you mentioned, where it's adding in and to. But I think the clarity of it might be worth the cost?

I'm curious what @seanCodes has to say...

Same. I'll quote the above so he get's another notification.

[scrolls back]....four years.

Word.

One of you choose with what he could at that time:
https://github.com/seven-phases-max/less-plugin-lists

@TigersWay - Which is great, but it's also using what was available i.e. not making something the parser would have choked on, so it borrows from mixin syntax. Ideally there would be something native. Even though this issue has been open four years, IMHO less loops have been.... less than ideal... from the beginning. Most looping examples are just iterative mixin guard hacks. Again, just my opinion.

Yes

at that time

But after 4 years, voting/deciding with 2 souls is kinda short.
And I'm no one :-)

Most looping examples are just iterative mixin guard hacks.

Not hacks; just recursion-based loops.
It's how you'd write a loop in a functional programming language.

Less draws rather heavily from FP principles thanks to its declarative nature. Contrast with the imperative nature of Sass.

Not hacks; just recursion-based loops. It's how you'd write a loop in a functional programming language.

@rjgotten Okay, fair point. But I mean is that while they can be evaluated recursively, they make for awkward syntax construction. But also, to be fair, even a declarative templating language like XSLT has a for-each tag. (https://msdn.microsoft.com/en-us/library/ms256166(v=vs.110).aspx) So a for-each-type evaluation is not imperative only. Even in Less, it's still declarative because it's essentially "binding" the iteration to the for-each evaluation. The only difference in an imperative for-each is that you could do something like mutate the state of values or break the loop. In a declarative loop evaluation, each iteration is "expanded" and evaluated against a condition. So you can never "break" the loop, but you can set up conditions that restrict which values pass.

In other words, the way to think about for-each as it applies to Less is like:

.rule {
  .mixin(extract(@list, 1));   // This was an `each` call of some sort on @list
  .mixin(extract(@list, 2));
  .mixin(extract(@list, 3));
  .mixin(extract(@list, 4));
  .mixin(extract(@list, 5));
  .mixin(extract(@list, 6));
}

Everything's going to be evaluated and merged into the parent's rules. That's why I was advocating for an "anonymous mixin" form that allowed guards.

I will say that I do share your concern that we should be hesitant about a syntax that _resembles_ an imperative form. It should clearly communicate the idea of "expansion" of list into calls. Like, in Less, you would never want something like:

@for (@i = 0; @i < length(@list); @i = @i + 1) {}

That's _completely_ imperative and makes no sense. In particular, in Less @i = @i + 1 makes no sense. A variable can have one value per scope. A declarative language can't or shouldn't mutate its own state.

:for (@i in 1 to 10) {} is a little different because @i is being assigned a single value per mixin expansion.... but.... it's still "imperative-like"; this is why I was favoring a bit some kind of "assignment" from :each, like :each(@list): (@val) or :each(@list) > (@val) or :for(@list):select(@val) or something like that.

@matthew-dean
You're absolutely right. There's a critical difference between a hard imperative for loop and the more declarative nature of a for-each loop. I wanted to actually write more about that, but you beat me to it.

@rjgotten ^_^ Since you opened this thread, do you have any suggestions for it moving forward? It's the kind of feature I'd like to see, but the syntax seems to be really difficult to get right.

@rjgotten Incidentally, when I was re-reading this that you wrote:

It seems that currently LESS only supports 'pure' rulesets to be passed along as mixin arguments or stored in variables as detached rulesets.

I should point out that in Less 3.5 beta, I implemented assigning mixin calls to variables or pass them into other mixins, per the proposal in https://github.com/less/less-meta/issues/12.

As in:

.foo {
  @call: .some-mixin(42);
  @call();
  .other-mixin(.some-mixin(42));
}

Does that help this issue at all or inform it as far as direction?

Does that help this issue at all or inform it as far as direction?

No, sadly it doesn't help.

For mixins to be able to work as 'lambdas' you need to be able to assign the mixin _itself_ as a parameter, not the _result_ of calling the mixin. E.g.

@list : a b c;
.iteration-mixin(@item) {
  // does something with each @item ...
}

:for-each(@list, .iteration-mixin);

And if we take unification of detached rulesets and mixins all the way and can support 'anonymous mixins' (a.k.a. detached rulesets) that are capable of taking parameters, then we can support syntax such as:

@list : a b c;
:for-each(@list, (@item) {
   // does something with each @item ...
});

I believe that would be the pinacle of user-friendly syntax the language could offer for looping over lists.

@rjgotten I'm not opposed to that form either, which I know is similar to what you first introduced. I'd probably prefer to shorten it to :each, personally. Is there anything you feel more semantically or declaratively correct for for-each over each?

But, pragmatically, I get where you're going for consistency, and including the ruleset as part of a "call" form is not bad.

I also agree, as you can imagine from our discussions in other threads, in the generalized form of (@var) { } that could be a language construct used anywhere (as "anonymous mixins").

Would you be against this form?

@list : a b c;
:each(@list, (@item) {
   // ...
});
:each(@list, (@item, @key) {
   // ...
});

I'm a little concerned about where this fits in the language space alongside :extend() and & when.

I just don't like @each. I'm not against a plain each() function as the parsing needs fewer changes (if detached rulesets were allowed args), but I also understand the pushback from some on seeing plain functions at the root. Glad to see this discussion still moving forward though!

Ooh, I like that! I think if we’re just passing the ruleset, it should be a function. I don’t think we should introduce another selector-like feature that takes no ruleset. :extend(); is really weird that way.

@calvinjuarez Can you clarify? You would advocate for a plain each() function, given those parameters?

You know; if you think about, then :each( ... ) really _is_ just a plain function at the root.
It's a function called ":each" (including the colon).
So implementation-wise there should be no real difference, right? If so, then the feature could be easily switched to with/without colon and maybe even both with the colon being optional.

That brings me to another point: Maybe we should make that colon an optional feature for _any_ rooted function call?

First look at a non-rooted function: property : func( ... )
And then look at a rooted function: : func( ... )

It's the same thing, with one difference: the rooted version has the left-hand side 'missing', because in fact the result is _directly_ assigned to — i.e. output into — the current scope, rather than assigned to a property.

@rjgotten You can already put functions in the root since 2.7. I'm not sure why we would make the colon optional for any function? Do you think that's preferable in terms of syntax?

Implementation-wise, yes there'd be a small change in the parser for function recognition if a colon was optional, but that's not a reason to not do it if it's preferred for this case.

One thing, we may communicate clearer to plugin authors that you CAN put functions in the root if we make it a plain each() function.

Come to think of it, maybe we should bail on using the colon-prefix here.
It may introduce ambiguation in the grammar, since it resembles pseudo-classes such as :nth-child()

And really; nobody wants the headache of having to deal with _that_ again, somewhere down the line.

Come to think of it, maybe we should bail on using the colon-prefix here.
It may introduce ambiguation in the grammar, since it resembles pseudo-classes such as :nth-child()

👍

Okay, so are we closer to consensus on making this a built-in each() function that passes in what we're calling either an "anonymous mixin" or a "detached ruleset with parameters"? I think it should be more the latter in the code-base until we can unify the two ruleset-call types.

If we're closer, I'd like to post this question:

Given that someone may have key/value lists, or want an index, which is more the "Less way" ->

each(@list, (@value) { }); // and...
each(@list, (@key, @value) { });
// or
each(@list, (@value) { }); // and...
each(@list, (@value, @key) { });

Unlike JavaScript, in Less the number of arguments is significant. So there's no programmatic reason to always put value first, because key won't be _set_ (passed as a parameter) unless there's a _matching definition_.

So I'm inclined to say (@key, @value) is more Less-y. If you have 2 args in your definition, then 2 args will be passed in. If not, value will be passed in.

I'd actually go for (@value, @key) since @value is in 99% of the cases what you'll be iterating over and @key is some secondary thing you might be using in the process.

It also has parity with forEach iteration in JavaScript.
And even though Less isn't JavaScript; let's face it, the both of them operate in the front-end and there'll be a lot of instance of shared mind-space for developers there. Might as well make it easier for them.

I'd actually go for (@value, @key) since @value is in 99% of the cases what you'll be iterating over and @key is some secondary thing you might be using in the process.

That's fine with me. I'm good with the argument that @key is secondary and not entirely about JS parity, although it's fine that it shares mind-space, as you say.

I like where this is! Yeah, I’m for the plain function.

each(@list, (@item, @i) {});

Plain and simple.

@rjgotten @calvinjuarez

Incidentally, I did a version of this with no parsing changes a little over two years ago. See the commits on this branch: https://github.com/matthew-dean/less.js/tree/feature/each

The way I did it with no parsing changes is that the second param could be a list, as in: each(@list; key, value; {}); Buuuut I recognize that's not ideal.

The thing is, if we want (@item, @i) {} to be a legit value, I see a few problems:

  1. Is (@item, @i) {} a form of detached ruleset value that can be used anywhere? If it's only used with each() and can't be used with any other function, why?
  2. If it can be used elsewhere, what is it? Is it a mixin or a detached ruleset, because a) if it's a detached ruleset, the rules for arguments are non trivial. If it's not the same args as mixins, why not? b) if it's a mixin, well how does that work? Can it have guards? Basically all of this thread needs to be resolved: https://github.com/less/less-meta/issues/16

Basically, that form would make a ruleset more like a mixin, which is okay, since we want to make one "thing". But then a single set of rules needs to be defined.

So, we could resolve by not allowing this "shape" of value anywhere else, and special-casing the built-in each(). (It wouldn't be the only special-cased function, as if can receive "when conditions", and no other function can.) Alternatively, we could avoid any parsing changes and "inject" 2 special vars into the block, just like @arguments is now. That's essentially what I did by default in my implementation. We could inject @key and @value into each iteration of the ruleset. Honestly, that's easiest, because I already did the work lol.

So the easiest, which can be implemented today, is basically:

each(@list, {
  prop-@{key}: @value;
});

Unless we special-case / parse each, but special-casing functions and a new rule for each args takes time.

Thoughts?

Thoughts?

Injection of the two parameters seems fine as a first step to get the feature out and working, and then revisit to add proper user-configurable parameter names once the detached ruleset vis-a-vis mixins issues are cleared up.

@rjgotten

Injection of the two parameters seems fine as a first step to get the feature out and working, and then revisit to add proper user-configurable parameter names once the detached ruleset vis-a-vis mixins issues are cleared up.

That's fine by me. @key and @value okay?

If we're only going to loop over lists, I'd use @index rather than @key,
If we're going to support looping over properties (e.g. of a detached ruleset, used as a map/dictionary) than the more general @key is more appropriate.

If we're going to support looping over properties (e.g. of a detached ruleset, used as a map/dictionary) than the more general @key is more appropriate.

That's exactly what I was thinking; that it would support looping over lists or rulesets-as-maps.

@rjgotten We could also inject a different var name depending on list type, as long as it was documented which one to use. I've seen languages do that. It would be trivial to determine if it's a ruleset or not as the list.

Actually, we may want to do do @key AND @index (and @value), in case someone wants to do something with the position of a rule in a map. For simple lists, the value of @key could be set to @index, for least surprise.

@matthew-dean
(...)
you're absolutely right. 'rulesets as maps' are ordered maps by definition, so an index makes sense for them as well.

Actually, we may want to do do @key AND @index (and @value), in case someone wants to do something with the position of a rule in a map.

This makes me wonder what @index means in the context of a map. That is, in a list loop, you could call extract() and pass the index. It seems like there are a lot of caveats and inconsistencies introduced this way (e.g. extract(@looped-thing, @index) works in some loops, but not all).

@map: {
  foo: bar;
  baz: qux;
};
each(@map, {
  -less-log: extract(@map, @index); // → ???
  // Or would this mean anything? (Certainly after a unification of access and extract that I've seen floated.)
  -less-log: @map[@index]; // → ???
})

So, in the implementation of this, I ran into an issue of nested each(), and wondering if we need named params. In an attempt to name params using (@value, @key) { }, I realized that was waaaay ambiguous for parsing, if we wanted to later do "anonymous mixins" or "detached rulesets with args". As soon as you write @dr: (@value ... up until then, this could finish with @dr: (@value + 1); or @dr: (@value, @key) { }, so it forces backtracking.

That is to say, two points:

  1. Named vars are required if you ever want to nest each() functions. As a first implementation, they're probably not needed? Maybe not address nesting at first? But if you want them later.... how are we going to reconcile injected vs. declared?
  2. I just wanted to make the point that this form for anonymous mixins/dr-args @dr: (@value, @key) { } is really too ambiguous.

Maybe something more like this in the future:

@dr: #(@arg1, @arg2) { };
@dr(1, 2);

each(@list, #(@value, @key) { });

So, the thing is, if you ever WANT nesting, then maybe injecting vars is the wrong approach. And if you need named vars, then just a plain (@value, @key) { } for DR args is the wrong approach IMO for Less.

Or maybe this is the wrong approach to support each(). I dunno...

@dr: #(@arg1, @arg2) { };

I was thinking about some specifier to make it clear you're defining a mixin. I'd think . should also be acceptable, since standard "nonymous" mixins allow either . or #.

In other words, conceptually, we're just expanding the _current_ mixin form to include no char after the . or #, though they wouldn't behave the way standard mixins would, in that they wouldn't be "author-callable", and they wouldn't allow "overloading" ('cause they'd be defining unique one-off mixins instead of additions to existing ones.

@calvinjuarez

This makes me wonder what @index means in the context of a map. That is, in a list loop, you could call extract() and pass the index. It seems like there are a lot of caveats and inconsistencies produced this way.

I don't see any inconsistencies produced there. You can change your example to something that works with the current PR.

@map: {
  foo: bar;
  baz: qux;
};
.box {
  each(@map, {
    -less-log: @map[$@key]; 
  })
}

outputs:

.box {
  -less-log: bar;
  -less-log: qux;
}

You can also do:

@list: a b c d;
.box {
  each(@list, {
    -less-log: extract(@list, @index);
  })
}

Which outputs:

.box {
  -less-log: a;
  -less-log: b;
  -less-log: c;
  -less-log: d;
}

Or are you simply pointing out that you can't actually use @index to extract() on a map? Because I think that's a completely separate issue.

In other words, conceptually, we're just expanding the current mixin form to include no char after the . or #, though they wouldn't behave the way standard mixins would, in that they wouldn't be "author-callable".

Probably could be author-callable eventually, but not-callable could work for this first use-case? I wondered which to go with: #( or .(, but I like the idea of either/or.

-less-log: @map[$@key];

$@var works!? I had no idea! I thought it was nixed! That makes me so happy!

$@ works!? I had no idea! I thought it was nixed! That makes me so happy!

Ha, yeah I slipped it in bug fixes before I released 3.5. ^_^

Probably could be author-callable eventually, but not-callable could work for this first use-case?

I'd think "anonymous" would suggest "not author-callable", but maybe "anonymous" isn't the word. Maybe this is something different.

Also (and I edited the other comment with this thought, too, but I'll put it here so it isn't as hidden), I think the _major_ difference is that .(){} and #(){} wouldn't be overloadable/stackable.

each(@list1; .(@value, @key, @i) {})
each(@list2; .(@value, @key, @i) {}) // this mixin probably shouldn't overload, right?

And _that_ would mean authors would have no specific way to reference and call a specified .(){} mixin, which means "anonymous" is what they are and we can just call them that straight-up.

@calvinjuarez

Wouldn't we eventually do this?

@mixin: #(@op1, @op2) {
  value: @op1 + @op2;
}
@mixin(2px, 1px);

The reason being what you said: i.e. vars are not overloadable. So you could essentially "replace" mixins by reassigning the variable.

@mixin: #(@op1, @op2) {
  value: @op1 + @op2;
}
& {
  @mixin: #(@op1, @op2) {
    value: @op1 - @op2;
  }
  @mixin(2px, 1px);
}

Seems like a fairly useful pattern. You could also pass around mixin definitions to other mixins, which you can't do now. (In 3.5 you can pass in mixin _calls_ i.e. the resulting ruleset to a mixin, but not definitions.) That's where I was going with it; that is, the reason why a prefix is needed.

If detached rulesets are useful to pass around and call, then it makes sense that rulesets w/ args would be just as useful/powerful, if not more so.

That's why I would want this pattern, if used for each(), to be something that could be generalized at some point.

I'd think "anonymous" would suggest "not author-callable"

The "anonymous" in "anonymous function" comes from the fact that it is constructed without its own named reference.

Without its own named reference, it cannot be directly accessed through name by code at other locations. In that sense, such a function is "not _directly_ author-callable," but is still callable if the author assigns the original function reference to a variable.

So yes; "anonymous mixin" is definitely the correct terminology here.

@rjgotten Thanks, that's a great explanation.

What do you think about the syntax tweak / PR?

I think the added # or . makes a lot of sense and not just from a parser disambiguation point of view.

.( a ) { } is to .foo( a) { } in Less as function( a ) { } is to function foo( a ) { } in JavaScript.

It literally reads as: "here I declare a mixin" ( the leading . ) "which I won't give a name for further reference."

The only alternative syntax that would make sense is imho to clone the arrow-function syntax from JS, C#, etc. and make it like:

each(@list; (@value, @key, @index) => { })

That would work as well, but is probably much harder to implement as it requires a lot of look-ahead through the arguments list to find the => marker, whereas if you find the .( or #( sequence at the head you already know you'd be dealing with an anonymous mixin without much - if any - lookahead required.

That would work as well, but is probably much harder to implement as it requires a lot of look-ahead through the arguments list to find the => marker, whereas if you find the .( or #( sequence at the head you already know you'd be dealing with an anonymous mixin without much - if any - lookahead required.

I mean this is my main reason; #() / .() is just easier to implement, but I wouldn't push it if the syntax didn't gel with people. If it gels, then I think it's working as expected in https://github.com/less/less.js/pull/3263. Please review when you get the chance!

Was this page helpful?
0 / 5 - 0 ratings