Proposal  List Comprehension
Hi, I have an idea to propose but not sure how it can be implemented. Still, I wanted to contribute a new syntax feature for ECMAScript with a functional programming feature: "List Comprehension".
Introduction
I know there are languages adopt the feature such as Python:
aList = [1, 2, 3, 4, 5]
doubled = [ x * 2 for x in aList ]
'aList' is the existing list, but we can use the List Comprehension syntax to create another list.
Simply put, it is a feature for creating a list (or array) structured object from another list structured object.
I know we can already use basic syntax in JS like the operation Array.prototype.map
, but I wanted to propose a more advanced use case which combines with the ESMAScript standard.
Additionally, I searched from the internet and found out that the MDN doc already has a similar feature called "Array Comprehension". Again, I know this feature is obsoleted in the browser implementation, but I wanted to reference how this feature has experimented in the past.
The examples from the docs are:
/* Normal Case */
[for (i of [1, 2, 3]) i * i ];
// [1, 4, 9]
var abc = ['A', 'B', 'C'];
[for (letters of abc) letters.toLowerCase()];
// ["a", "b", "c"]
/* Multiple Parameters Case */
var numbers = [1, 2, 3];
var letters = ['a', 'b', 'c'];
var cross = [for (i of numbers) for (j of letters) i + j];
// ["1a", "1b", "1c", "2a", "2b", "2c", "3a", "3b", "3c"]
/* Conditional Case */
var years = [1954, 1974, 1990, 2006, 2010, 2014];
[for (year of years) if (year > 2000) year];
// [2006, 2010, 2014]
[for (year of years) if (year > 2000) if (year < 2010) year];
// [2006], the same as below:
[for (year of years) if (year > 2000 && year < 2010) year];
// [2006]
The following contents are about what I have come up with.
Basic Syntax
I think the proper way is to represent in a more mathematical way:
[ i * i  for (i of [1, 2, 3]) ];
// [1, 4, 9]
var abc = ['A', 'B', 'C'];
[ letters.toLowerCase()  for (letters of abc)];
// ["a", "b", "c"]
Secondly, instead of using multiple for...of...
expressions, we can simply use a little bit of destructuring style to simplify the cases:
/* Multiple Parameters Case */
var numbers = [1, 2, 3];
var letters = ['a', 'b', 'c'];
var cross = [ i + j  for ([i, j] of [numbers, letters])];
// ["1a", "1b", "1c", "2a", "2b", "2c", "3a", "3b", "3c"]
However, it is important to note that i
represented the elements iterated from the numbers
array; j
represented the elements iterated from the letters
array.
Lastly, when it comes to the conditional case, I think we can do something like:
/* Conditional Case */
var years = [1954, 1974, 1990, 2006, 2010, 2014];
[ year  for (year of years) if (year > 2000)];
// [2006, 2010, 2014]
[ year  for (year of years) if (year > 2000 && year < 2010) year];
// [2006]
Additionally, the conditional case above just filter the element out of the list and didn't do any additional computation. I think it can be simplified like this:
/* Conditional Case */
var years = [1954, 1974, 1990, 2006, 2010, 2014];
[ for (year of years) if (year > 2000)];
// [2006, 2010, 2014]
[ for (year of years) if (year > 2000 && year < 2010) year];
// [2006]
However, if it didn't have the conditional case and didn't do any computations, such as:
[ i  for (i of [1, 2, 3]) ];
// [1, 2, 3]
I think it shouldn't be simplified in:
[ for (i of [1, 2, 3]) ];
// Wrong case
Reasons are:

i
isn't used, so declaringi
doesn't have any meaning  The operation above is the same as cloning an array which I think it could just use
[...[1, 2, 3]]
However, if it comes to the multiple parameter case, then:
// Finding the coordinates in the first coordinate quadrant
[ [x, y]  for ([x, y] of [xPositions, yPositions]) if (x > 0 && y > 0) ]
Can be reduced as:
// Finding the coordinates in the first coordinate quadrant
[ for ([x, y] of [xPositions, yPositions]) if (x > 0 && y > 0) ]
Since the destructured format [x, y]
in for ... of ...
matches the desired result, the "[x, y] 
" can be omitted.
May be confused with Bitwise OR operator? (which is 
)
I think this might not be the problem, because in this article, the list comprehension syntax idea, the 
should always followed by the keyword for
.
[ a  b  for ([a, b] of [list1, list2]) ]
However, it might be best to distinguish by using parentheses:
[ (a  b)  for ([a, b] of [list1, list2]) ]
Alternative List Comprehension Syntax which I've Came Up With
 Omit the

[ x ** 2 for (x of aList) ]
 Using
where
[ x ** 2  where (x of aList)]
 Omit the

and usingwhere
[ x ** 2 where (x of aList) ]
But I personally still wanted to stick with for...of...
syntax because of the following cases which combines with ECMAScript standards I'm going to address.
Combining with ECMAScript Standards
The above are the basic cases are covered. I know we can use similar approaches such as Array.prototype.map
or Array.prototype.filter
to deal with listlike structure creation.
However, since more and more ECMAScript standard covered features such as ES6 Iterators and Generators, ES7 AsyncAwait and ES9 Asynchronous Iterations, I have several ideas came up.
Feature 1. Use Iterator object as the existing list
function *genExample() {
yield 1;
yield 2;
yield 3;
}
[ x ** 2  for (x of genExample())];
// [1, 4, 9]
for...of...
loop can accept and iterate through objects with Symbol.iterator
property (because it can generate the Iterator object).
And if iterator object is acceptable, which means:
// Originally JSON object isn't iterable
let info = { name: 'Max', age: 20, hasPet: false };
// We can declare its own generator function to generate the iterator
info[Symbol.iterator] = function*() {
for (prop in this){
yield [prop, this[prop]]
}
}
// It can now be plugged into the for...of... loop
for (let [p, v] of info) { console.log(p, v) }
// Which means it can also be used in List Comprehension:
[ `${prop} is ${value}`  for [prop, value] of info ]
Feature 2. Generating an iterator from List Comprehension
Similar to how we declare the generator function, we can use a similar approach to declare an iterator object:
const list = [1, 2, 3];
let iterator = *[ i ** 2  for (i of list) ];
// Using for...of... loop
for (let i of iterator) {/* ... */}
// Or simply use the builtin APIs like the next method, etc...
Feature 3. Await the Promise.all
like list structure
This feature is considered a little hard for me to come up with. Overall, I reckon that this might be a case where we can combine the Promise.all
utility with an expression to create a new array data structure.
async function example() {
const responses = await [ res.data  for (res of requests) ];
// Do something...
}
Feature 4. Use "Asynchronous Iterator" as the existing list
This feature states that it generates the asynchronous iterator via list comprehension.
const lines = async *[ line.trim()  for await (line of readline) ];
Basically, its a bit like generating an iterator from this asynchronous function:
async function* () {
for await (const line of readline) {
yield line.trim();
}
}
Adopt Function Programming Feature
1. Eta Conversion
JavaScript already had embraced this feature, for instance:
// Declare a function which calculates the squared value of number
function squared(value) { return value ** 2; }
const list = [1, 2, 3];
// Map each element with squared value
list.map(x => squared(x));
Can be simplified into:
list.map(squared)
Because it applies the elements from the array and directly inputs into another function.
So, as in the list comprehension case, the code:
[ squared(x)  for (x of list) ];
Can also considered to simplified into this way:
[ squared  for (x of [1, 2, 3]) ];
But then the x
will violate the rule which I have mentioned in this article. The x
isn't used in this case. So it kind of a bit awkward to come up with another solution:
[ squared  for (list) ]
// It should also be the same as:
// [ squared  for list]
//
Well, I'm not sure if this is appropriate, but please see the next feature, although it could be a very far future to propose and entering the implementation phase.
Feature 2. Function Composition in List Comprehension
Normally, in mathematics, assume we have function f(x)
and g(x)
, we can use f・g
to represent f(g(x))
.
To achieve function composition with list comprehension idea in this article:
function capitalize (str) {
return str[0].toUpperCase() + str.substring(1);
}
function exclaim (str) {
return str + '!';
}
let names = ['maxwell', 'martin', 'leo'];
[ exclaim(capitalize(name))  for (name of names) ]
If we want to do it in a more elegant way by using Eta conversion, we need to do something like:
function customConversion(str) {
return exclaim(capitalize(str));
}
[ customConversion  for names ]
However, considering there are existing proposals focusing on pipeline operator which is about function composition, I think we can simplify to:
[ capitalize > exclaim  for names ]
Summary
I think that's all, this is my idea about contributing ECMAScript by proposing List Comprehension feature.
I know this article is long, but your patience and kind feedbacks would be very appreciated. :)