I am trying to make a variadic mixin in LESS. To this end I use the following syntax for my mixin :
.mixin (#a; #rest...) {
// #rest is bound to arguments after #a
// #arguments is bound to all arguments
}
But I don't know how to manipulate #rest and read to the last parameters of the mixin.
This is my code :
.color () { }
.color (#shade) {
#id {
background-color : rgb(#shade, #shade, #shade);
}
}
.color (#shade, #rest...) {
#id {
background-color : rgb(#shade, #shade, #shade);
}
.color(#rest);
}
.color(200, 160);
As you guess, this mixin should examinate the entire list of parameters, and colour the background of my <div id="id"> with the shade of grey corresponding to the last parameter of the mixin (in this case, rgb(160, 160, 160)).
But when I compile this code with less-1.4.1.js, I get the following error :
SyntaxError: error evaluating function `rgb`:
color functions take numbers as parameters
So how to access to the second, third, fourth... parameters of the mixin ?
Thanks a lot for your advices, and have a nice week-end !
EDIT
This works perfectly, thanks a lot !
However I would like to ask an other question. Let's say that my mixin is variadic to the extent that it requires at least one parameter which has nothing to do with the rest of the arguments (e.g. a string or an other number), but which has to be processed, so that possible calls to the previous mixin could be :
.color("first argument", 200, 160);
.color(-42, 200, 160);
.color(3, 200, 160); // 3 doesn't need to be processed in the loop
In other words, the .loop should examinate all the parameters of the mixin starting from the second and apply a different process to the first argument. So I need to change the skeleton of the mixin into something like this :
.color(...) {
...; // Handling the first parameter
.loop (#arguments); // Handling the rest of the parameters
}
But in your solution, the variable #arguments contains the entire list of arguments, including the first. How to exclude it from the list, without playing on isnumber() ?
I precise that actually in my project, each of the parameters starting from the second are processed, so that :
.loop(#list, #index: 1, #shade: NULL) when not (isnumber(#list)) and (isnumber(extract(#list, #index))) {
.loop(#list, (#index + 1), extract(#list, #index));
}
becomes
.loop(#list, #index: 1, #shade: NULL) when not (isnumber(#list)) and (isnumber(extract(#list, #index))) {
.loop(#shade);
.loop(#list, (#index + 1), extract(#list, #index));
}
and this process doesn't consist in simply changing the background color of a fixed <div> ;) But I wanted to simplify my question.
Thanks a lot for your answers and precious advices !
Edit, again : what you recommend to me works perfectly, Martin. Thanks again !
Less gets confused with your second and third .color mixin, as they can both take just one argument, and if #rest is passed to the mixin as an argument and is not numeric (i.e. a list or empty) it causes more problems. Also #rest... and ... are tricky with multiple mixins with the same name - it is better to pass the arguments to another set of mixins (as a list to a single argument) and then switch between them using guards or the number of arguments they can take.
I would structure the mixins a bit differently (and add a helper mixin .loop that does the looping according to what is passed to .color).
The two mixins would then work like this:
.color: passes all arguments in #arguments to a single argument in mixin .loop
.loop: if the argument is neither a list nor a numeric value -> no output
.loop: if multiple arguments in a list -> loop through the list until you reach the last numeric value (or rather stops when it meets the first nonnumeric argument)
.loop: when reached the last parameter return output based on its value
.loop: if parameter is a single value -> return output based on the single parameter
in Less this could be done like this:
.loop(#list, #index: 1, #shade: NULL) when not (isnumber(#list)) and not (isnumber(extract(#list, #index))) and not (isnumber(#shade)) {}
.loop(#list, #index: 1, #shade: NULL) when not (isnumber(#list)) and (isnumber(extract(#list, #index))) {
.loop(#list, (#index + 1), extract(#list, #index));
}
.loop(#list, #index: 1, #shade) when not (isnumber(#list)) and not (isnumber(extract(#list, #index))) {
.loop(#shade);
}
.loop(#shade) when (isnumber(#shade)){
#id {
background-color: rgb(#shade, #shade, #shade);
}
}
.color (...) {
.loop(#arguments);
}
and if you call now something like this:
.color(120,160);
the output CSS will look like this:
#id {
background-color: #a0a0a0;
}
which corresponds to the value of the last argument -> rgb(160,160,160)
This now has an output only for the last argument in the list. If you want to do something for each argument, you do so in the second .loop mixin (the actual loop) and can get rid of the third one that is used only to separate the last iteration step from the rest of the loop.
Edit:
To your additional question "How to deal differently with certain arguments and what to do when they are not numeric?" - > General answer: you can always adjust the guards, and add additional mixins for the specific cases.
For example, if you want to treat the first argument (or any argument) differently using the .loop, you can add an additional .loop mixin - like this:
.loop(#list) when not (isnumber(#list)) {
/* do something with the first argument */
#first_argument: extract(#list, 1);
/* start the loop at the second argument:*/
.loop(#list, 2);
}
with the rest left as is, this should do what you want:
save your "first argument" from the #arguments into a variable (or you can do with it whatever you want)
start the loop at the second argument and continue until it reaches the last numeric argument (as shown above)
As said before, this is just an example illustrating how to do what you asked ... but you will have to play with it and design the guards and mixins accordingly to your desired outcome and your specific problems.
In the loop you can do something to each argument ... and you can do different things to numeric and nonnumeric arguments (you just need to adjust the conditions in the guards), you can check also if the arguments have specific units and so on. All this is simple as you are merely iterating through a list using the extract() function and incrising the #index variable.
In the case the first argument / first couple arguments have a specific assignment you can use #rest in the same way as I show with #arguments and do something with the first argument already in the .color mixin before you send the #rest to the .loop. You would do something like this:
.color (#first, #rest...) {
/* do something with the first argument */
#first_argument: #first;
/* send the rest to the loop */
.loop(#rest);
}
Related
Is a function that changes the values of an input argument still a pure function?
My example (Kotlin):
data class Klicker(
var id: Long = 0,
var value: Int = 0
)
fun Klicker.increment() = this.value++
fun Klicker.decrement() = this.value--
fun Klicker.reset() {
this.value = 0
}
Wikipedia says a pure function has these two requirements:
The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices.
Evaluation of the result does not cause any semantically observable side effect or output, such as mutation of mutable objects or output to I/O devices.
From my understanding, all functions from my example comply with the first requirement.
My uncertainty starts with the second requirement. With the change of the input argument, I mutate an object (rule violation), but this object is not outside of the function scope, so maybe no rule violation?
Also, does a pure function always need to return a completely new value?
I presume, this function is considert 100% pure:
fun pureIncrement(klicker: Klicker): Klicker {
return klicker.copy(value = klicker.value++)
}
Be gentle, this is my first Stackoverflow question.
The increment and decrement functions fulfill neither of the requirements for a pure function. Their return value depends on the state of the Klicker class, which may change while program execution proceeds, so the first requirement is not fulfilled. The evaluation of the result mutates the mutable Klicker instance, so the second requirement is also not fulfilled. It doesn't matter in which scope the mutable data is; a pure function must not mutate any data at all.
The reset function violates only the second requirement.
The pureIncrement function can be made pure if you change it to:
fun pureIncrement(klicker: Klicker): Klicker {
return klicker.copy(value = klicker.value + 1)
}
I have a helper compare that returns a css class that simply highlights the text. "better" makes it green, "worse" colors it red. Basically the function compares 2 numbers (the compare function thats commented out does the same as the ternary below it). How can i compare multiple values in the same helper function? I know i could just create a bunch more helper functions and compare all the data 1 by 1, but im sure theres a better way. Heres what the template looks like :
Return the multiple values as an object from your helper then refer to the keys in your template.
js:
Template.myTemplate.helpers({
compare(){
return { key1: value1, key2: value2, ... keyN: valueN};
}
});
html:
{{compare.key1}} etc...
You'd have to pass them as arguments within the function definition itself, something like this should do the trick:
compare: function( number1, number2 ) {
return number1 > number 2 ? "better" : "worse";
}
In SCSS, I have a mixin that takes a list and checks its length, as follows:
#mixin foo($list){
#debug length($list);
}
When I pass a list of more than two lists ($a, $a) as follows,
$a: (1, 2, 3);
#include foo(($a, $a));
the length function counts that there are two $a-s inside of ($a, $a), and returns the result:
DEBUG: 2
but when I pass a list that consists of a single list ($a) as follows,
#include foo(($a));
it seems that the list $a is decomposed, and the length function counts the three elements in $a instead of counting the number of $a-s in ($a), and returns the result:
DEBUG: 3
It does not make difference if I embed the list further. All of the following return the same result:
#include foo($a);
#include foo(($a));
#include foo((($a)));
Is this an expected feature? Why does this happen, and is there a way to return 1 in these cases?
In Sass, parentheses are used to indicate order of operations, not to indicate that you have a list with a single element.
In Sass 3.3, adding a trailing comma will automatically turn your value into a list:
$foo: 1, ;
What you may be wanting to do instead is have your mixin take a variable number of arguments and use them as a list. That would look like this:
#mixin foo($list...){
#debug length($list);
}
.foo {
#include foo(1);
}
.foo {
#include foo(1, 2);
}
.foo {
#include foo((1, 2));
}
The console gives the desired results:
DEBUG: 1
DEBUG: 2
DEBUG: 1
If you do this, however, the list must be the last argument of the mixin (or function).
I using the less mixin which generate a random number within given range.
here is the code:
.makeRandom(#min: 0, #max: #min+1) {
// Math.floor(Math.random() * (max - min + 1)) + min
#getNum: `(Math.floor(Math.random() * (#{max} - #{min} + 1)) + #{min})`;
}
and Calling in another mixin set animation on calling div:
.move (#left : 45% , #duration: 5s ,#delay : 2s) {
animation: moveUp #duration linear #delay infinite;
/* Safari and Chrome: */
-webkit-animation: moveUp #duration linear #delay infinite ;
.makeRandom(100, 400);
margin-left: unit(#getNum ,'px');
}
but I am getting this error
SyntaxError: error evaluating function unit: the first argument to unit must be a number
when I tried to change the unit. how can I changed unit of #getnum?
I noticed this problem in v1.5.
Since v1.5 you can not pass non-numeric values (not passing isnumber()) to functions like unit() that accept only numbers as arguments, even though it worked in earlier versions.
And - numeric values in javascript interpolation were not recognized as numeric by LESS <= 1.6.0, and isnumber(`2`); would have returned false.
So, this two put together are the reason why numbers returned by javascript did not work in v1.5, but worked prior v1.5 and work again in LESS v1.6.
But if you need to use it in 1.5 you can "cast the type" by using a function that does accept strings or the "non-numeric numbers" returned by javascript interpolation:
#str: '230'; //or `230`
#getNum: (percentage(#str)/100);
this adds the percentage sign % at the end ... but it does not really matter if you just want to use it for the number ... as now you can pass it to any function that accepts only numbers ... in your case using unit() you can change the unit to whatever you like.
margin-left: unit(#getNum , px);
However, an even simpler solution in your case would be to just skip the unit() function and concatenate the unit to #getNum:
margin-left: ~'#{getNum}px';
The CSS output would in both cases look like this:
margin-left: 230px;
I'm not sure if it was your case too, but since I landed on this page looking for an answer to the same error about 4 years later I decided to post this here so that it might help someone else in the same predicament 😊.
So, in our case we were getting this error in expressions like this:
width: unit(#navbarHeight - 0.1, em);
And the solution was to simply enclose the mathematical expression (in this case, subtraction) in parentheses, like this:
width: unit((#navbarHeight - 0.1), em);
#AsGoodAsItGets is right, as stated in the 4.0 changelog.
Parentheses are required (by default) around division-like expressions, to force math evaluation.
I had a look at twitter bootstrap mixin.less where they generate the grid. There's this line of code:
.offsetX (#index) when (#index > 0) {
(~".offset#{index}") { .offset(#index); }
.offsetX(#index - 1);
}
.offsetX (0) {}
Does anyone know what does that .offsetX (0) for?
Since .offsetX (#index) is recursive when the index is greater than 0, and iterates by subtraction, this case represents the terminal point of the recursive function.
If you set #index to 12, it will write .offset12, .offset11,..., .offset1 and write nothing for .offset0.
Edit
As the preprocessor attempts to resolve the mixin call for .offsetX (0), the when keyword is going to guard against using the recursive mixin. If another mixin isn't found, an error will result, sort of like calling an undefined function.
Hence, the terminating case of .offset (0) {} is there to cleanly exit the recursion.
If you'd like to play with it, here's a basic demo with which you can try commenting out the line with the terminal case, and then observe the exception on rerunning.
JSFiddle