Less.js: Mixin return values

Created on 28 Dec 2011  路  59Comments  路  Source: less/less.js

Can I set a return value as a color? As in:

.myselector {
   border: 1px solid .custommixin(@colorValue);
}

Just wondering in response to another user's issue regarding color functions: https://github.com/cloudhead/less.js/pull/488

feature request medium priority needs decision stale

Most helpful comment

@distransient There has been more in-depth discussion around designing solutions for this in: https://github.com/less/less-meta/issues/16.

(Less-meta is a repo for higher-level goal planning for Less.js, rather than individual issues or bug reports.)

All 59 comments

Oh I see, no. But you can do..

.myselector () {
  @return: 2px;
}

.class {
  .myselector;
  border: @return solid red;
}

pretty ugly though.

Ah, of course. It might be nice to have a returned value for custom color functions (and other custom value functions) when mixin guards are in place. @return could be a reserved variable for LESS. As you say, it's a bit ugly to just have a mixin do nothing but variable assignment when it's called.

Would be nicer to see the first example I posted supported at some point. The rationale being that you have spin, lighten, darken, etc functioning as built-in mixins, so the logic for return values is already there in LESS, it would just be opening up the door for custom mixins.

Not I think that programming said functionality is easy mind you, just that it would empower people to extend the logic a bit.

yes, true, but I also like how it currently is, where the 'functions' are all javascript..

Couldn't variables accept arguments the same way mixins do? My usecase would be for instance:

@boxGlow(@spread: 5px, @color: #f00): inset 0 0 @spread @color;
.myselector {
   box-shadow: 1px 1px 1px #000, @boxGlow(2px, #00f);
}

It's also fairly analogous with JavaScript where functions live in regular variables.

@cloudhead: If I spent some time implementing that feature (I haven't looked at the Less source yet, so I can't promise anything), would you be willing to add it if the code is good enough?

Adding a return capability to mixins would preserve the mapping of mixins to JavaScript functions, while still facilitating @jacobrask and @matthewdl's use case. I've documented this a bit more on issue 637.

duplicate of #538

woops, this is #538

This would be a very, very useful solution.

I'm trying to implement dynamically resizing images (like [UIImage resizable...] on iOS) and I would like to use a function which will return an URL string, not property/value pair.

Curently I'm using @cloudhead's solution.

Just to tie this in a nice bow, this should be included when the options.json support is added. Issue: #850

Mixins are mixins, we must not conflate their usage with functions. If having the ability to define custom functions in the style sheet is something we want though, that can be a separate discussion.

Mixins are mixins, we must not conflate their usage with functions.

The problem is that there's no way to forbid using mixins as functions. It is possible to use one as the other and they (and me :) get to use that ability. So de facto it's only a matter of syntactic sugar and verbosity:

.sum(@a, @b) {
    @-: (@a + @b);
}

usage {
    width: @-;.sum(20px, 33px);
}

Using same "function" twice in the "same scope":

// short (since 1.6.2):
usage {
    & {width:  @-;.sum(55px, 77px)}
    & {height: @-;.sum(11px, 99px)}
}

// oldschool (since 1.4.x):
usage {
    .-() {width:  @-;.sum(20px, 33px)}
    .-() {height: @-;.sum(40px, 42px)}
    .-();
}

Is there a reason to invent some new "Less function declaration/definition" syntax (different from a mixin definition syntax) only to not allow to use width: .sum(20px, 33px); directly?

What the what did you just do there. That's some next-level Less-hackery going on there, lol.

@lukeapage I am sorry I can't understand the state of this issue. You've put a reference to this issue on several others but this is set as closed. Is there a plan to allow to use mixins as lambdas or are you suggesting us to take advantage of the global variable setting workaround? In other issue I've understand you were planning to remove that on some future.

@seven-phases-max your approach is awesome but there is no way to pass to the sum mixin the result of calling sum two times, am I wrong?
Talking on a somewhat more universal language, this: foo(bar(1, 2), bar(3, 4))

@axelcostaspena Well, everything is possible using mixins, it's just a matter of verbosity (obviously 5 or 6 lines of code do not look like a practical replacement for a oneliner).

less-plugin-functions

Wow. Nice! Really, the only thing I'd improve syntax-wise if this is moved to core, is turning the outer block into @functions and probably dropping the leading period on the mixin declarations for consistency with how these functions are used at call sites (namely; also without the leading period, as real Less functions).

E.g.

@functions {
    foo(@x) {
        return: @x * 2;
    }
}

This still leaves the issue of having functions/mixins assignable to variables and passable as parameters, so that they can be used as lambda delegates. But this right here is already a very nice way to segregate 'computation' mixins from 'render' mixins.

Wow. Nice! Really, the only thing I'd improve syntax-wise if this is moved to core, is turning the outer block into @functions and probably dropping the leading period on the mixin declarations for consistency with how these functions are used at call sites

Yeah, we should respect this from @cloudhead :

Mixins are mixins, we must not conflate their usage with functions.

Defining a function as ".foo" by putting it in ".function" and then calling it with "foo" doesn't really make sense as a syntax.

@rjgotten The one suggestion I would make to your syntax is to not make functions plural, even if there is more than one function in the function block. Seems more Less/CSS consistent.

@function {
    foo(@x) {
        return: @x * 2;
    }
}

Also, as sugar:

@function foo(@x) {
     return: @x * 2;
}

I assume that these functions simply get added to the Less function registry as normal JS functions that evaluate a Less expression?

@rjgotten @matthew-dean : Lovely syntax, I'd been pondering how one could "nicely" add functions to less for a while. That syntax seems clear, intuitive and unambiguous.

One question though - is the return: part necessary?

One question though - is the return: part necessary?

Hmm, how do you return a value w/o writing a return statement?

Hmm, how do you return a value w/o writing a return statement?

The function could simply be an expression.

@function foo(@x) { @x * 2  }

That might actually be cleaner. So, basically, the function could be a way of abstracting expressions that evaluate to a value, instead of a block of prop / value pairs. I agree, the return: property seems like kind of a hack of the language.

Reopening since it seems like we're reconsidering this as a possibility.

@matthew-dean Or maybe we really should listen to @cloudhead and create a new ticket? (Probably it was my mistake of linking here instead of creating a new one... I guess I've just missed the actual reason it was closed the last time)...

foo(@x) { @x * 2 }

Oh, no:

foo(@x, @y) { 
    @z * @w; 
    @z: pow(@x, @y);
    @w  pow(@z, @y); // wtf, I just missed : here
}

There's reason why almost every other language has a return keyword.

Yes, if you wanted to have multiple expressions in a function, then that syntax wouldn't work. You're arguing against something I didn't propose. You asked how one would have a function without a return statement, and here's how:

@function foo(@x, @y) { 
   pow(@x, @y) * pow(pow(@x, @y), @y)
}

Not sure about the {} braces if it was just an expression, but yes, it could be done. SHOULD it be done is another question. And yes, we could create a new issue about functions. Whatever makes sense to you.

A different idea just popped into my head. I happen to be working on a legacy VBScript app, and the return value is assigned to the function name. This might make more sense in the context of "assigning" a value (if you wanted multiple expressions), since return: is kind of stretching the concept of a return statement. As in:

@function foo(@x, @y) { 
    @z: pow(@x, @y);
    @w: pow(@z, @y);
    foo: @z * @w; 
}

That way, it's not a "magic" property, but an actual assignment of value to the function. @battlesnake @seven-phases-max - what do you think of that?

And maybe a single expression evaluation could be sugar? Best of both worlds? Brevity and flexibility?

@function foo(@x) { foo: @x * 2;  }
// same as
@function foo(@x) { @x * 2; }

As much as I dislike "automatic return" in Ruby and Groovy, this is one place where it may be nice - that being said, I'm not really opinionated either way about it, I just like few keywords LESS has. Given the time I've wasted debugging Grails code where bugs were caused by someone accidentally returning a value where they didn't want to, I somewhat agree with your reasoning for keeping some kind of explicit return syntax. I like that VB-syntax idea (never thought I'd say those words...).

Allowing both the VB syntax, and the "return" might be nice too, like how Delphi supported "function name := value" and "Result := value" with Result being a magic variable for the return value.

My current use case was for a tacky little "web 2.0" gradient generator:

@function web2-control(@direction, @start, @end, @split: 50%, @spread: 5%) {
    return: linear-gradient(@direction, @start 0%, @start ((@split-@spread)), @end ((@split+@spread)), @end 100%)
}

.stupid-button {
    background: web2-control(to bottom, white, darken(skyblue, 10%));
    &:hover {
        background: web2-control(to bottom, white, skyblue);
    }
}

Judge the problem, not the intent of the code haha :)

@battlesnake It's funny, your linear-gradient example is the kind of thing that I was thinking about as far as a good use case. There are times when returning a prop/value pair is cludgy when you want to operate on a portion of a value, especially when the values themselves accept multiple values. Other examples would be the transform syntax. Sometimes mixins aren't a perfect fit.

And yes, VBScript isn't my favorite either, but a good artist pulls inspiration from everywhere. :-)

foo: @z * @w; That way, it's not a "magic" property, but an actual assignment of value to the function.

It's still the same "magic property". Difference in charcode values between f.o.o and r.e.t... won't make any logical shift (not counting that if function name overlaps with some existing property it becames another point of a reader confusion... Also count a duty to rename foo to bar each time you copy some snippet from one function to another. So personally I can't see what could be wrong with return... It's just a "set the return value" identifier.)

As for making it optional. OK, I see now what you mean... Well, yep, this is one of those... ah, never mind: just -1 :)

I wanted the same style of linear gradient for buttons in any state, just with colour or brightness changed, so:

@function web2-gradient(@direction, @start, @end, @split: 50%, @spread: 5%) {
    return: linear-gradient(@direction, @start 0%, @start ((@split-@spread)), @end ((@split+@spread)), @end 100%)
}

@primary: red;
@contrast: 10%;

@light: lighten(@primary, @contrast);
@dark: darken(@primary, @contrast);
@lighter: lighten(@primary, ((@contrast*2)) );
@darker: darken(@primary, ((@contrast*2)) );

@button-normal-bg: web2-gradient(to bottom, @light, @dark);
@button-hover-bg: web2-gradient(to bottom, @lighter, @primary);
@button-down-bg: web2-gradient(to bottom, @darker, @primary);

I actually looked at moving to Stylus after finding that I can't create value-returning functions in Less, but Stylus doesn't have that declarative feel that I love about less.

I'm leaning more towards the return: value syntax now - but for the one-liners, could we take on an OpenSCAD syntax like:

@function square(@x) = @x * @x;

So we make the return explicit there too by using an = and ; instead of braces.

Or to be more CSS-like:

@function square(@x) : @x * @x;

Using colon instead of equals symbol.

It's still the same "magic property".

Er, it isn't, since return is a made-up property, and foo is defined by the user, but okay. I'm not that attached to it. I just kinda wish that return didn't look like a property, and then isn't. But other than assigning to the function name, I don't have a better proposal.

@battlesnake The second option would be my preference. That makes more sense. I still wonder if single line expressions might be preferable for language simplicity. Functions could always be chained (the resulting value sent to another function).

So, I don't know. Defining a syntax for another multi-line block to output just a single value seems.... I don't know, Less already has so many kinds of blocks. Are there really many real-world examples where a multi-line function is needed? The multi-line example was still solvable in a single line. And, if indeed, there's some need to build a complex, multi-line function, would that not be better served by an inline plugin? As far as the Less language, the need for a return statement just feels like a bad omen to me.

So, are there really real-world cases where:
a) you want to evaluate an expression,
b) it's too difficult or complex to do it in a single line (or by chaining functions)
c) it can't be in a JS plugin
?

oneliners

I still can't see any reason why I'll be _forced_ to write
... (@a + @b + @c) * (@a + @b + @c);
instead of normal:

@x: @a + @b + @c;
return @x * @x;

would that not be better served by an inline plugin?

Here's simple example to illustrate how easy Less functions can kick JS functions for many use-cases (actually always unless it's about performance or some functionality that simply does not exist in Less itself),
(artificial) Less function (less-plugin-functions format) to return a squared opacity/alpha of a color:

.function-sqralpha(@c) {
    @a: alpha(@c);
    return @a * @a;
}

Now the code necessary to do same in JS:

functions.addMultiple({
    sqralpha : function(c) {
        var a = c.alpha; // no docs and no guaranty this internal stays the same, but there's no easy way to reuse built-in Less `alpha()` here
        return new tree.Dimension(a * a); // Dimension? some kind of quantum mechanics?
    }
});

And in fact the more complex (in regards of involving more and different in, intermediate and out Less value types, built-in functions reuse etc.) a function becomes the more pain it's to write in JS.

So to be honest for me the recent ideas look ehm, ... I'm not even sure... strange? :)

@matthew-dean : OpenSCAD only allows single-line functions I think, but you can get the same capability of muilti-line functions by using let blocks in OpenSCAD.

Come to think of it, OpenSCAD's syntax for for iteration and for let blocks might be useful for LESS, given that the two languages are both declarative ways to define geometry and to style it.

@seven-phases-max I'm still skeptical, and I feel like the burden of proof is on the multi-line examples. That said, return @x * @x; feels better than return: @x * @x, because it's more logically clear that the function is receiving / returning the value, rather than being assigned to a property that does...something.

But a return statement feels less and less like Less every passing moment.

As to this:

I still can't see any reason why I'll be forced to write
... (@a + @b + @c) * (@a + @b + @c) ;
instead of normal:

@x: @a + @b + @c;
return @x * @x;

The question is better stated: what is the compelling reason to add more syntax to the language just to split that expression onto two lines? The burden should be to prove a more verbose syntax with extra keywords is necessary, not the other way around. There's nothing "normal" about it, as it doesn't exist yet. And it's still not close to a real-world example. @battlesnake's example was still only a single-line expression.

@function {
    web2-control(@direction, @start, @end, @split: 50%, @spread: 5%): 
        linear-gradient(@direction, @start 0%, @start ((@split-@spread)), @end ((@split+@spread)), @end 100%);
}

I like the idea of functions, but it would be disappointing if we added something heavier to the language than was absolutely needed / useful. We already have 3 types of blocks that "do things": mixins, classes/ids than can be "evaluated" and imported into scope (which makes them sort of non-parameter mixins, but they are somewhat different), and detached rulesets. If you add a functions block, all of these definitions have inconsistent syntax from each other. A mixin isn't defined with an @ keyword but a function is. A detached ruleset block is assigned with a colon (and must terminate with a semi-colon), but a mixin block isn't.

I think this should take a step back and look at the syntax as a whole. A @function syntax with a return statement may be okay, but I don't see the logical alignment between all parts of Less and this syntax. And is there logical alignment between Less functions and plugin functions? Somewhere it feels to me like we're missing some cohesion. Do you know what I mean?

That is, just consider how this looks for a moment:

.class {
  prop: val;
}
.class(@x, @y) {
  prop: @x * @y;
}
@class: {
  prop: val;
};
@function class(@x, @y) {
  return @x * @y;
}

I know that a function must be specially defined to distinguish between other blocks, but maybe that's the problem. If function says @function then mixin should say @mixin. Adding just one puts the parts of evaluated blocks in disagreement. So I'm kind of a :-1: if this is going to be the case.

@matthew-dean I'm a bit lost then. So how exactly

@function web2-gradient(@direction, @start, @end, @split: 50%, @spread: 5%)
    : linear-gradient(@direction, @start 0%, @start ((@split-@spread)), @end ((@split+@spread)), @end 100%)

instead of

@function web2-gradient(@direction, @start, @end, @split: 50%, @spread: 5%) {
    return: linear-gradient(@direction, @start 0%, @start ((@split-@spread)), @end ((@split+@spread)), @end 100%);
}

solves all that problems you mentioned? Maybe then it would make sense to start with questioning the @function itself and not {}?

I did not comment @function (did you forget who suggested @function above? ;) initially just because my opinion about it as radical as always (so now it will start another syntax fight :):

To be honest personally I'd be more happy with either .function .foo, function foo, function:foo or finally foo:function (weird but at least follows :extend). Adopting Sassish way of using @at-rule syntax for new Less "directives" seems to be just _too_ late for Less even if potentially it's indeed much less CSS-conflicting as anything else.


but I don't see the logical alignment between all parts of Less and this syntax

? So you don't really see any similarity between Less mixins and (whatever syntax) functions? I can guess many users do not understand parametric mixins at all thinking of them as some kind of a primitive "CSS-properties-batch" only, but in this particular function/mixin context, hmm...

Either way:
less-plugin-functions does the whole thing in ~50 lines of code (if I remove all the plugin specific hacks) exactly because it does reuse existing core code for parsing and evaluating mixins. Providing all those _existing and documented_ features (you're not obligated to use) for free.
So considering this, now I only wonder how:

.class {
  prop: val;
}
.class(@x, @y) {
  prop: @x * @y;
}
@class: {
  prop: val;
};
@function class(@x, @y) : @x * @y;

can be potentially better to justify most likely more bloating implementation (+ new parsing code) to provide more limited functionality?


And finally, to back up "feature/syntax consistency" I guess that a reformatted table of existing Less feaures the way I see them would be interesting (just in case):

name   {prop: val}  // a ruleset having some standard CSS selector as a name
@name: value;        // variable
@name: {prop: val}; // *unnamed* ruleset assigned to a variable as its value

// mixin
.name(@x, @y) {
    prop: @x * @y;
}

// function (whatever `func` directive)
<func> name(@x, @y) {
    return: @x * @y; // return property of the function
}

Having no prefix for function names would be consistent with the current state of LESS, i.e.

color: darken(@base, 10%);
background: checkerboard(1em, lighten(@base, 20%), white);

Having no prefix for JS functions, while requiring a prefix for functions supplied in the actual LESS code seems somewhat backwards.

I personally don't see any use for multi-line functions, but I may be having a "who needs >640k of RAM" moment here.

If there was a demonstrable need for multi-line functions in future, after we had adopted the @function name(params) : expression; syntax, we could always permit it by using SCAD-style let blocks:

@function multi-line-thing(@a, @b, @c) :
    let { @sum : @a + @b + @c; @product : @a * @b * @c; @mean : @sum / 3; }
        @sum / @product ~", " @mean;

That syntax would still be @function name(params) : expression;. The syntax for expression would be extended to permit let { assignments } expression.

Variables in such blocks are scoped to the expression following the block only, they don't leak out to parent scope.

This way we could add single-expression functions now, but we still have the option to extend them to multi-line in future (if a need was demonstrated), by either using "recipe" block-style functions (with return) or "sub-scope" functional-style functions (with let).

If block-style was to be used, I like the return: value; syntax over the return value; syntax - since everything that isn't a block is still a key: value pair, with return (or maybe result) just being a special key name within functions. The explicit @function syntax then ensures that return: is not treated as a CSS attribute name.

Everything is ruleset-like, including functions, which just look exactly the same as mixins. Really, when boiled down, the sole discriminators are that mixins can set CSS properties and that functions have to have a return statement. (You cannot and should not do both.)

So why not just rely on that behavior directly?

E.g.

.mixin(@a, @b) {
  value : @a @b;
}
.func(@a, @b) {
  return @a @b;
}

Then let the compiler figure out at the _call site_ whether it is calling a mixin or a function and raising an exception if one or the other is used in an inappropriate context.

Alternatively, if exceptions aren't your thing:

  • Define the CSS output of a function called as a mixin as an empty ruleset.
  • Define the function return value of a mixin as the collated ruleset of all its applied CSS properties and nested rulesets evaluated into their final state, returned as a detached ruleset.

Hmm polymorphism... it makes sense I guess, mixins can't appear on the right-hand side of a colon and functions can only appear there. As long as we ensure that errors are raised if one is used in the wrong place, using mixin syntax for functions seems ok.

Really, when boiled down, the sole discriminators are that mixins can set CSS properties and that functions have to have a return statement.

This is what I was getting at. Should we create a new ruleset type that has a different method of "define"?

But... I also agree with this:

Having no prefix for JS functions, while requiring a prefix for functions supplied in the actual LESS code seems somewhat backwards.

I've always felt that the requirement that a mixin be prefixed with . or # is ugly and annoying. (See the first part of: https://github.com/less/less.js/issues/1342.) It's also strangely arbitrary that only two out of three types of common selectors can be converted to mixins. That's only relevant here if we allowed mixins to return values.

However, there is some reasoning to have a separate type, since function vars should not be imported into the caller scope, and functions should not have guards, as it's hard to intuit what that would even mean. So there are other differences other than being able to return values.

I'm not necessarily against @function, obviously I endorsed it earlier in the thread. I'm just wondering how this all fits together, and I want us to be cautious. Note that my criticism is not to turn us away from using any one thing; it's to make sure we're asking all the necessary questions and thinking critically.

Even CSS breaks it's own rules sometimes, and contains things like having the vertical-align property cause two entirely different behaviors. So, Less can create new conventions as well. I just want to help make sure any new solutions keep Less easy, intuitive, conservative, and concise.


I'm a fan of limiting to single-line expressions, but if it seems too arbitrary and restrictive to others, then the multi-line form that seems most clear probably is indeed:

@function {
  foo(@a) {
    return @a;
  }
}
// and
@function foo(@a) {
  return @a;
}

Maybe the best argument for a dedicated "define" is that, different from mixins, a function definition can't or shouldn't appear multiple times (in the same scope). So they're more like @plugin functions in that regard. So, because the properties are different enough from mixins (private vars, single definition, return statement), maybe co-opting mixins just wouldn't work.

Why not allow literal javascript inside less? Basically, it'll allow you use all javascript features inside CSS to create functions. Example:

function abs(a) { return Math.abs(a); }

.test { attribute: abs(-5px); }

Note two things:

  1. Function are literal javascript. It can be recognized by something like: "function /\w+/ ..." and stopped by last "}". The problem is identify where "{" start and where "}" ends. Because we can have some internals structures, like if, for, etc.
  2. I don't thinked about how parameter will be passed to function. For instance, what javascript will receive if I pass 5px? And about pass less functions, like abs(convert(5px, pt))?

@rentalhost
First of all, inline javascript statements are allowed in the less.js implementation:

div {
    @x: -10.5;
    width: unit(`Math.abs(@{x})`, px);
}

Secondary, Less is designed as a CSS superset, and CSS has very few to do with C-like languages and JavaScript in particular. Thus direct JavaScript statements like function abs(a) {return Math.abs(a);} will be not just confusing and inconsistent there, but also _directly conflicting_ with native Less/CSS syntax.

So two basic reasons, why a code like above should never be recommended to use and a JS-like syntax should never be considered when designing Less syntax, are obvious:

  1. Inline javascript statements _require_ underlying JavaScript environment for the compiler (e.g. such statements will never be supported in PHP or C# or whatever else ports of Less. This is basically spoils the very purpose of the Less itself, which is designed as a platform/environment agnostic self-contained language).
  2. Fundamental "types / language entities" of CSS and JS are simply orthogonal at best and yet again directly conflicting at worst. As you already noticed yourself, there's no way for JavaScript to directly support values like 5px, red, url(//foo/icon.png) and so on and so on. So to pass CSS values and statements to "literal javascript" you will have to provide an intermediate syntax and conversion facility to merge those orthogonal things in the same language. It's not actually a big deal to design such a language, but it will have nothing to do with Less (which is yet again designed as a CSS superset and has _nothing_ to do with JS _at all_).

In other words, all our discussions above are not a result of so called "not invented here" syndrome, but just an attempt to fit possible function definition syntax to already existing Less syntax-base as smoothly as possible.

Please please please:

.halfway-between(@large, @small) {
    max(@large, @small) - (abs(@large - @small) / 2);
}

:smiley:

@andrewhosgood What happens here?

.halfway-between(@large, @small) {
    max(@large, @small) - (abs(@large - @small) / 2);
}
.halfway-between(@large, @small) {
    property: value;
    foo: bar;
}
.rule {
    width: halfway-between(3, 2);
}

Ultimately, a mixin is a parameterized ruleset that can have multiple matches, and returns multiple values, and a function name can only have one match (per scope) and can only return one value.

I think @battlesnake had the simplest and most straightforward syntax proposal:

@function square(@x): @x * @x;

A slight adjustment so that the scope of @x isn't confused:

@function square(@x) { @x * @x; }

Or (possibly) so that we don't confuse with JS-based functions:

@expression square(@x) { @x * @x; }

I think single-use expressions would be simplest to grok, and wouldn't pollute understanding of mixins. And, would also serve most of the examples given. Basically, you're just short-handing (aliasing) expressions for quick re-use. Thoughts? I do wonder if there's a way to "define" the expression name without an at-rule, though. Hmm.

@matthew-dean Point taken. Just trying to use existing patterns. Amending my request then, would it be:

@function halfway-between(@large, @small) {
    max(@large, @small) - (abs(@large - @small) / 2);
}

Not worried about syntax, but this would then allow me to do the thing I can't do currently.

Syntax is the hardest part to get right. And you want to get it right because it's a new language feature.

I'm a little wary of @function now, more than I was when this discussion was active in the past, just because of @plugin-defined functions and existing functions in Less.js. And without a return statement it's not very function-y.

@expr square(@x): { @x * @x };
@expr halfway-between(@large, @small): {
    max(@large, @small) - (abs(@large - @small) / 2)
};

Thinking over it more, I think the colon : assignment and the semi-colon ; terminator is more logical. In that square(@x) is being assigned the value of the evaluated expression. This would seem to keep it more in line with detached rulesets. The braces {} indicate that the scope of @x is not the surrounding scope of the statement, but what is being assigned through the expression alias.

I like this simpler concept of re-usable expressions rather than "functions" (which already means "defined by the parsing language" i.e. JavaScript). So @expression or @expr makes sense to me here. I think re-usable expressions has more wide-spread utility and is easier to understand.

I like this simpler concept of re-usable expressions rather than "functions"

Basically; they're parametrized pre-processor macro expansions like in plain old C, right? But hopefully _without_ the crazy abuse that came with those, since Less will have @plugin functions as a far more decent alternative for the heavier lifting and true complex functions.

@rjgotten

a far more decent alternative for the heavier lifting and true complex functions.

https://github.com/less/less.js/issues/538#issuecomment-107247753. Though never mind.


And I still object the inventation of the special syntax hardcoded to specific "one liner" examples. Still smells like "I love to invent new cool syntaxes" syndrome.
After all it's just asking for more issues from the parser implementation point of view... For instance recall #2785 and think how ambiguous

{
   foo();
   foo() - bar();
   bar();
}

becomes actually. There you'll have to introduce a lot of new parsing rules to detect which statement is actually the function returning value and which are just root function calls (not even counting a friendly error messages if something goes wrong).
So in short, I think everything I wrote in https://github.com/less/less.js/issues/538#issuecomment-107255583 is still in effect.

And yes, stressing the Sassish @at-rule directive yet again - never been in Less yet, so why is it going to be now? (It seems like someone is just using too much PostCSS recently ;).

@seven-phases-max

{
   foo();
   foo() - bar();
   bar();
}

I was implying that multi-statements would be invalid here (would evaluate to a single expression), so that wouldn't apply, BUT....

...stressing the Sassish @at-rule directive yet again - never been in Less yet, so why is it going to be now? (It seems like someone is just using too much PostCSS recently ;).

I haven't been using PostCSS, but yes, writing both @function and @expression made me feel somewhat dirty. As I mentioned when this issue was re-commented on, much of the syntax discussion has moved even further away from this possibility. Less just doesn't have a matching syntactical paradigm for this, because everything is declarative. Meaning, even in a return statement in a mixin, these two statements would reasonably be equal, since values are constants for a scope (once resolved).

.function-sqralpha(@c) {
    @a: alpha(@c);
    return @a * @a;
}
.function-sqralpha(@c) {
  return @a * @a;
  @a: alpha(@c);
}

So, either way, with what's discussed so far, it seems somewhat square peg / round hole. I don't think mixins can reasonably pretend to be functions (without some compromises and unexpected behavior), and defining functions (or expressions or macro expansions) via Less (that is, in your stylesheet) has proven to also be problematic.


On a separate note-

One thing that might lean us even a bit more to closing this thread: when I was looking back through comments, including the one you linked to, you had this as a not an ideal way to solve the above problem.

functions.addMultiple({
    sqralpha : function(c) {
        var a = c.alpha; // no docs and no guaranty this internal stays the same, but there's no easy way to reuse built-in Less `alpha()` here
        return new tree.Dimension(a * a); // Dimension? some kind of quantum mechanics?
    }
});

With Less 3.0, I've simplified returns so that any non-Node (other than falsey values) returned by a function is cast to an Anonymous Node, such that it's easily output into your final CSS. Obviously, if you want to include that result in another expression, you need to be explicit in the Node type. Buuuuut.... 3.0 also includes a "late parsing" feature (all values are Anonymous until referenced - this allows property referencing without creating extra parsing work or extra nodes), so that could be repurposed in order to just be like:

functions.addMultiple({
    sqralpha : function(c) {
        var a = c.alpha; // docs have started! doing my best!
        return a * a;     // Cast to Dimension because Less is cool... maybe?
    }
});

And considering 3.0 should be all about functions and @plugin, and the remaining list of things it needs to release... I would say at the least, this isn't the right time for this feature. I did my best to simplify the latest examples, but even with that, your objections are valid, and I'm ok to close for now.

@seven-phases-max

538 (comment). Though never mind.

No no; you raise a good point.

There's substantial overhead for the middle-ground, where you have more than a simple one-liner expression to do, but not enough to warrant firing up a JS plugin and writing code that handles all the node-type mappings.

While @matthew-dean raises a good point that node return types could be inferred, that still feels like a lot of overhead for some simple math or string interpolation. (In particular the latter; string interpolation _requires_ a temporary variable, so you can never write a one-line expression for it.)

If working a usable return into the syntax is the problem, then why not skip the explicit return statement altogether by _specifying what name the out-variable will be bound to_:

.answer(@a, @b):@c {
  @c   : ~"The answer is @{sum}";
  @sum : @a + @b;
}

This feels natural with the order independence and declarative nature of the Less language. It also directly extends the signature of mixin declarations in a non-ambiguous manner, which the parser can look for and which avoids the need for an @expression or @function keyword or custom at-rule.

This also means it can work together with mixin overloading and multicast calls. If a mixin is called like a function, it _requires_ that a declaration exists which has both a matching parameter signature _and_ a colon-separated out parameter specified. And if multiple matching overloads exist, the return value becomes a list of the relevant calls in mixin declaration order. (Just as how multiple matching overloads currently output rules in declaration order.)

Huh. That's actually not so bad.

Question: what happens then with properties, values, and other variables declared in the mixin (if called as a function). Safe to presume they're isolated from the caller scope (since properties couldn't be omitted in the location of the call, if the call is within a value expression)?

That's not bad. We expanded functions to be called everywhere. So it could be that mixins could be called everywhere with the same checks to see if the resulting output node is legal in that location. So then functions = JS and mixins = LESS.

I also liked how you addressed and resolved multiple matching mixins (a list as the result). Clever. And declarative.

I was ready to give up on this idea but this is really not bad.

We could also update the documentation that mixins, by default, "outputs" a ruleset, unless an output var (or vars, or $properties??) are specified.

Has there been an update for this?

@distransient There has been more in-depth discussion around designing solutions for this in: https://github.com/less/less-meta/issues/16.

(Less-meta is a repo for higher-level goal planning for Less.js, rather than individual issues or bug reports.)

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.

Was this page helpful?
0 / 5 - 0 ratings