📜 ⬆️ ⬇️

CoffeeScript: Detailed Cycle Guide

CoffeeScript: Detailed Cycle Guide

As you know, CoffeeScript offers a slightly different set of control structures than JavaScript .



Despite the fact that the language developers maximally simplified the grammar and gave a detailed description of all the instructions , making a more or less non-standard cycle for many remains a great difficulty.


')






In this article I will try to tell as much as possible about the principles of working with cycles in CoffeeScript and closely related control structures.



All code is accompanied by comparative examples in JavaScript .




For-in instruction


Let's start with the simplest for loop:



for (var i = 0; i < 10; i++) { //... } 

In CoffeeScript, it will be written like this:



 for i in [0...10] 

Ranges are used to determine the number of iterations.
In our case, the range from 0 ... 10 means: perform 10 iterations of the loop.


But what if you want to specify a condition of type i <= 10 ?



 for i in [0..10] 

At first glance, nothing has changed, but if you look closely, you will notice that in the range of one point has become less.



As a result, we get the following entry:



 for (var i = 0; i <= 10; i++) { //... } 

If the initial value of the range is greater than the final [10..0] , then we will get a reverse cycle with an inverted result:



 for (var i = 10; i >= 0; i--) { //.. } 

I want to note, it is also possible to use negative values ​​of the range:



 for i in [-10..0] 

And so, you can fill the array with negative values:

 [0..-3] #[0, -1, -2, -3] 

Now consider the real situation, using the example of a function that calculates the factorial of the number n:



Javascript:

 var factorial = function(n) { var result = 1; for (i = 1; i <= n; i++) { result *= i; } return result; }; factorial(5) //120 


CoffeeScript:

 factorial = (n) -> result = 1 for i in [1..n] result *= i result factorial 5 #120 

As you can see from the example above, the CoffeeScript code is more compact and readable compared to JavaScript .



However, this code can be slightly simplified:



 factorial = (n) -> result = 1 result *= i for i in [1..n] result 

In this article, this is not the last example of factorial calculation, more efficient methods will be discussed a little later.



[...]

I will dare to step back a little from the topic and mention another interesting point related to the use of the [...] (slice) construction.



Sometimes to someone else's code you can find something like this:



 'a,b,c'[''...][0] 

Which of course will mean the following:



 'a,b,c'.slice('')[0]; //a 

At first glance, it is quite difficult to distinguish ranges from slices. There are two main differences:



First, in the slices, you can skip one extreme value



 [1...] 

Here I would like to pay special attention to what we get after the translation of this expression:



 var __slice = Array.prototype.slice; __slice.call(1); 

This can be convenient for many situations, for example, to get a list of function arguments:

 fn = -> [arguments...] fn [1..3] #0,1,2,3 


I want to note that in CoffeeScript to get a list of function arguments there is a safer and more elegant option ( splats ):

 fn = (args...) -> args fn [1..3] #0,1,2,3 

It is also acceptable to use arithmetic and logical operations:

 [1 + 1...] 


Secondly, an object is allowed before slices.



 [1..10][...2] #1,2 


Thirdly, it is acceptable to use transfers in slices.



 [1,2,3...] 

This example performs a simple concatenation operation:

 [1, 2].concat(Array.prototype.slice.call(3)); //[1,2,3] 

More useful example:



 list1 = [1,2,3] list2 = [4,5,6] [list1, list2 ...] #[1,2,3,4,5,6] 


List comprehension


The most striking syntax for working with objects in CoffeeScript is List comprehension .



An example of how you can get a list of all factorial calculations from 1 to n :



 factorial = (n) -> result = 1 result *= i for i in [1..n] factorial 5 #1,2,6,24,120 

Now let's look at a more interesting example and display a list of the first five members of the location object:



 (i for i of location)[0...5] # hash, host, hostname, href, pathname 

In JavaScript, this code would look like this:



 var list = function() { var result = []; for (var i in location) { result.push(i); } return result; }().slice(0, 5); 

In order to display a list of elements (not indexes) of an array, you need to specify one more parameter:



 foo = (value for i, value of ['a', 'b', 'c'][0...2]) # [ a, b ] 

On the one hand, list expressions are a very efficient and compact way to work with objects. On the other hand, you need to clearly understand what code will be obtained after the broadcast in JavaScript .



For example, the code above, which lists the elements from 0 to 2 , can be rewritten more effectively as follows:



 foo = (value for value in ['a', 'b', 'c'][0...2]) 

Or so:

 ['a', 'b', 'c'].filter (value, i) -> i < 2 

At this point, please pay special attention to the space in front between the method name and the opening bracket. This space is required!

If you skip the space, we get the following:

 ['a', 'b', 'c'].filter(value, i)(function() { return i < 2; }); //ReferenceError: value is not defined! 

Now, you might be wondering why the option with the .filter () method was the most preferable?


The fact is that when we use the for-of instruction, the translator substitutes a slower version of the loop than is required, namely for-in :



The result of the broadcast:


 var i, value; [ (function() { var _ref, _results; _ref = ['a', 'b', 'c'].slice(0, 2); _results = []; for (i in _ref) { value = _ref[i]; _results.push(value); } return _results; })() ]; 

Let's face it, the final code is terrible.


Now let's look at the code obtained using the filter method:



 ['a', 'b', 'c'].filter(function(value, i) { return i < 2; }); 

As you can see, we got the perfect and efficient code!



If you use CoffeeScript on the server, then you have nothing to worry about, if not, then it is worth remembering that IE9 does not support the filter method. Therefore, you yourself should take care of its availability!



Then operator


As you know, to interpret expressions , the CoffeeScript parser analyzes indents, line breaks, carriage returns, etc.



Below is a typical cycle for raising numbers from 1 to n to a power of two:



 for i in [1...10] i * i 

For clarity, we used the line break and indent.


However, in a real situation, most developers would prefer to write this expression in one line:



 for i in [1...10] then i * i 

In the while , if / else , and switch / when statements, the then statement indicates to the analyzer that expression is split.



Operator by


Up to this point we have considered only “ simple ” cycles, now it’s time to talk about cycles with missing values ​​in a certain step.



Let's output only even numbers from 2 to 10 :



 alert i for i in [0..10] by 2 #0,2,4,6,8,10 

In JavaScript, this code would look like this:



 for (var i = 2; i <= 10; i += 2) { alert(i); } 

The by statement is applied to the range of elements in which the iteration step can be set.



We can also work not only with numbers or array elements, but also with strings:



 [i for i in 'Hello World' by 3] #H,l,W,l 

The by and then operators can be used together:



 [for i in 'hello world' by 1 then i.toUpperCase()] # H,E,L,L,O, ,W,O,R,L,D 

Although this example is a bit far-fetched and in reality the “ one-to-one ” step should be done, nevertheless, the joint work of the by-then operators allows you to write very compact and efficient code.



Operator own


JavaScript often uses the .hasOwnProperty () method, which, unlike the in operator, does not check properties in the object's prototype chain:

 var object = { foo: 1 }; object.constructor.prototype.bar = 1; console.log('bar' in object); // true console.log(object.hasOwnProperty('bar')); // false 

Consider an example of using the .hasOwnProperty () method in the body of a for-in loop:

 var object = { foo: 1 }; object.constructor.prototype.toString = function() { return this.foo; }; for (i in object) { if (object.hasOwnProperty(i)) { console.log(i); //foo } } 

Although we added the .toString () method to the object object prototype, it will not be listed in the body of the loop. Although you can contact him directly:

 object.toString() //1 

CoffeeScript has a special operator for these purposes:

 object = foo: 1 object.constructor::toString = -> @foo for own i of object console.log i #foo 

If you need to use the second key of the for-of instruction, then it is enough to specify it separated by commas, while adding the own statement again is not necessary:

 for own key, value of object console.log '#{key}, #{value}' #foo, 1 


If / else conditional statements


Now I would like to draw attention to one very important point, which is associated with the sharing of cycles with if / else statements.



Sometimes in JavaScript applications we can find similar code:



 for (var i = 0; i < 10; i++) if (i === 1) break; 

We will not discuss, let alone condemn, developers who write like this.


For us, it is of interest only how to correctly write an expression in CoffeeScript .



The first thing that comes to mind is to do this:



 for i in [0..10] if i is 1 break # Parse error on line 1: Unexpected 'TERMINATOR' 

Fine ... but according to the rules of lexical analysis of CoffeeScript , an unexpected terminal value will be detected before the if statement , which will lead to a parsing error!



From the previous material, we remember that we can write the expression in one line using the then operator:



 for i in [0..10] then if i is 1 break #Parse error on line 1: Unexpected 'POST_IF' 

However, this did not help, we again see the error of parsing.



Let's try to figure it out ...


The point is that the if statement follows the same rules as other instructions for which the then operator is possible. Namely, in order for our expression to be parsed correctly, we need to add the then operator after the expression with if again:



 for i in [0..10] then if i is 1 then break 

So we get the following code:



 for (i = 0; i <= 10; i++) { if (i === 1) { break; } } 

Sometimes there are situations when, before a cycle, you need to check the execution of a kl. conditions:



 if (foo === true) { for (i = 0; i <= 10; i++) { if (i === 1) { break; } } } 

Due to the use of non - deterministic data processing and list expressions, we can present our code as follows:



 (if i is 1 then break) for i in [0..10] if foo is on 

Note that in this case we did not use the then operator, and no parsing errors occurred!



Conditional operator when


We have already considered the by and then operators, it is time to talk about the next statement in our list, namely the conditional when clause.



And we begin with the correction of the previous example:



 if foo is on then for i in [0..10] when i is 1 then break 

In this case, the code turned out to be a little more than the previous version, but it acquired a lot more expressiveness and meaning.



Let's look at another example of how you can derive the order of numbers from 1 to 10 modulo a natural number n:



 alert i for i in [1..10] when i % 2 is 0 

After the translation into JavaScript code:



 for (i = 1; i <= 10; i++) { if (i % 2 === 0) { alert(i); } } 

As you can see the use of the when clause gives us even more opportunities to work with arrays.



For-of instruction


You have already seen examples of using for-of instructions when looking at list expressions. Now let's take a closer look at the for-of instruction, which, along with for-in, allows you to iterate over the properties of an object.



Let's immediately draw a comparative analogy with the for-in instruction in JavaScript :



 var object = { foo: 0, bar: 1 }; for (var i in object) { alert(key + " : " + object[i]); //0 : foo, 1 : bar } 

As you can see, to get the value of the properties of an object, we used the following syntax: object [i] .


In CoffeeScript , everything is simpler; first, we can get the value of an object using list expressions:



 value for key, value of {foo: 1, bar: 2} 

Secondly, for more complex expressions we can apply a more detailed notation using the operators already familiar to us:



 for key, value of {foo: 1, bar: 2} if key is 'foo' and value is 1 then break 

In JavaScript, the same result can be obtained as follows:



 var object = { foo: 1, bar: 2 }; for (key in object) { if (key === 'foo' && object[i] === 1) { break; } } 

Another example of effective use of for-in :



 (if value is 1 then alert "#{key} : #{value}") for key, value of document #ELEMENT_NODE : 1, #DOCUMENT_POSITION_DISCONNECTED : 1 

Let me remind you that the most effective way to get the list of object properties is the keys () method:



 Object.keys obj {foo: 1, bar: 2} # foo, bar 

In order to get property values, the keys () method must be used in conjunction with the map () method:



 object = foo: 1 bar: 2 Object.keys(object).map (key) -> object[key]; # 1, 2 


While instruction


In addition to the for-of / in instructions, CoffeeScript also implements a while instruction.



When we looked at the for-in instruction, I promised to show an even more efficient way to calculate the factorial number n , the time is just right:



 factorial = (n) -> result = 1 while n then result *= n-- result 

To the bottom I want to add that the most elegant solution for calculating factorial is the following:



 factorial = (n) -> !n and 1 or n * factorial n - 1 


Loop instruction


We will not dwell on this instruction for a long time, because its only purpose is to create an infinite loop :



 loop then break if foo is bar 


Relat broadcast:



 while (true) { if (foo === bar) { break; } } 


Until instruction


The until statement is similar to the while instruction, with one exception, that negation is added to the expression.


This can be useful for example for finding the next character set position in a string:



 expr = /foo/g; alert "#{array[0]} : #{expr.lastIndex}" until (array = expr.exec('foofoo')) is null 

Relat broadcast:



 var array, expr; expr = /foo*/g; while ((array = expr.exec('foofoo')) !== null) { alert("" + array[0] + " : " + expr.lastIndex); } //foo : 3, foo : 6 

As you can see from the example, the loop is executed until the result of the expression reaches zero.



Do-while instruction


I will say right away that in CoffeeScript there is no implementation of the do-while instruction. However, with the help of simple manipulations, it is possible to emit its partial behavior using the loop statement:



 loop #... break if foo() 


Array methods (filter, forEach, map, etc.)


As it is known, all the same methods are available in CoffeeScript as in JavaSctipt .


There is no sense in examining this whole group of methods; we will only consider the general principle of operation using the example of the map () method.



Create an array of three elements and square each of them:



 [1..3].map (i) -> i * i 

Relat broadcast:



 [1, 2, 3].map(function(i) { return i * i; }); 

Consider another example:



 ['foo', 'bar'].map (value, i) -> "#{value} : #{i}" #foo : 0, bar : 1 

The second argument, the map method, accepts the calling context:



 var object = new function() { return [0].map(function() { return this }); }; // [Window map] 

As you can see this inside the map points to Window , to change the context of the call, you need to do this explicitly:



 var object = new function() { return [0].map(function() { return this; }, this); }; // [Object {}] 

In CoffeeScript, the special operator => is intended for this purpose:



 object = new -> [0].map (i) => @ 

Relat broadcast:



 var object = new function() { var _this = this; return [0].map(function() { return _this; }, this); }; 

In other words, use these array methods as far as possible.



I posted a cross- browser implementation of these methods on github'e


A real example of using the map and filter methods in CoffeeScript , can also be viewed in one of my projects on github'e



Do statement / closures


As you know, in JavaScript , closures are actively used, and CoffeeScript also does not deprive us of this pleasure.



CoffeeScript uses the do statement, which takes an arbitrary number of arguments to create an anonymous self-bound function.



Consider an example:



 array = []; i = 2 while i-- then array[i] = do (i) -> -> i array[0]() #0 array[1]() #1 

The essence of this code comes down to filling the array. At the same time, array elements contain not elementary values, but functions, each of which returns the index of its element.



JavaScript code would look like this:



 var array = [], i = 2; while (i--) { array[i] = function(i) { return function() { return i; }; }(i); } array[0]() //0 array[1]() //1 


Enclosed instructions


Nested instructions are not much different from other instructions and follow the same rules:

For example, fill in the array with paired elements from 1 to 3:

 list = [] for i in [0..2] for j in [1..2] list.push i list # [0,0,1,1,2,2] 

As you can see there is nothing difficult!

Perhaps you will want to write it in one line. Well, let's try to simplify the recording now:

 list = [] for i in [0..2] then for j in [1..2] then list.push i 

PS: I personally, would not use such a record, however, you should know that it is also possible to write this way, because sooner or later you will have to work with someone else's code.

What if you need to add an expression before the second cycle?

As an example, we derive three pairs of elements from 0-3:

 list = [] for i in [0..2] list.push i for j in [1..1] list.push i list #[0,0,1,1,2,2] 


This is the right option and there is nothing to improve. It’s also impossible to write everything into one line, because an explicit identification is required before the second cycle. But the body of the cycle can be written in short notation.

 list = [] for i in [0..2] list.push i list.push i for j in [1..1] list #[0,1,2,3] 


In the third line you can use both the prefix and postfix forms of the record.

jQuery, etc.


I will say right away, for CoffeeScript, it does not matter which JavaScript library you use.



Let's start with the most important jQuery function -. Ready ()

.ready ():

 $ -> @ 

The result of the broadcast:


 $(function() { return this; }); 

I don’t know about you, but this record almost always causes me to laugh



The next jQuery method in our list is .each () , which is almost equivalent to the standard .forEach () method

$ .each:

 $.each [1..3], (i) -> i 

The result of the broadcast:



 $.each([1, 2, 3], function(i) { return i; }); 


ECMASctipt 6


If you are not interested in the future development of the ECMASctipt 6 standard, feel free to skip this section.

As you know, in the future standard ECMASctipt 6 it is planned to implement generators, iterators and list expressions.
Firefox now supports most of the draft standard.

Why am I doing this?

The fact is that the future syntax of ES6 is practically more than completely incompatible with today's CoffeeScript.

For example, the instruction for ... of , now is more general than it is needed:

 [value for key, value of [1,2,3]] 

At the output we get the following perversion :

 var key, value; [ (function() { var _ref, _results; _ref = [1, 2, 3]; _results = []; for (key in _ref) { value = _ref[key]; _results.push(value); } return _results; })() ]; //[1, 2, 3] 

The future standard makes it possible to use iteration through objects, where it is easier:

 [for i of [1,2,3]] 

Wow, isn't it?

Expression generators will also be available:

 [i * 2 for each (i in [1, 2, 3])]; //2,4,6 

The following entry will be possible:

 [i * 2 for each (i in [1, 2, 3]) if (i % 2 == 0)]; //2 

Iterators will also be available:

 var object = { a: 1, b: 2 }; var it = Iterator(lang); var pair = it.next(); //[a, 1] pair = it.next(); //[b, 2] 

Iterators can also be used in conjunction with expression generators:

 var it = Iterator([1,2,3]); [i * 2 for (i in it)]; //1, 4, 6 


With the release of the new standard, many CoffeScript chips will cease to be so, and kernel developers obviously have a lot of work to do to keep their “sugar” positions. We wish them good luck.

Source: https://habr.com/ru/post/140764/


All Articles