JavaScriptでPython風の内包表記を作りたかった

Pythonのリスト内包表記をJavaScriptでも使いたくなりました。

リスト内包表記を使うと、「数値のリストから整数だけ抽出して2乗する」といった処理が簡潔に書けます。

```Python
num_list = [1, 4.3, 8.71, 9, 11]
print([i**2 for i in num_list if isinstance(i, int)])
# 出力：[1, 81, 121]
```

便利です。
ということでJavaScriptでも使えるようにしようとしました。
こんな感じです↓

```JavaScript
ac`i**2 for i of ${[1, 4.3, 8.71, 9, 11]} if Number.isInteger(i)`
```

名付けて「配列内包表記」。
見た目重視でテンプレートリテラル&タグ関数を採用しました。
こんな構文で使えます。

```JavaScript
ac`式 for 変数名 of ${イテラブルオブジェクト} if 条件式`
```
if以下は省略可です。
ぱっと見すごくいい感じに見えませんか？

実際はほぼ実用に堪えないんですけどね。
というのも、筆者には構文解析の知識がないので、どうしても凡庸な文字列処理でゴリ押すことになります。
不正な構文を検知したりといった機能もありません。
さらに、「おまけ」の項で述べますが、次のような呼び出しをすると動かなくなります。


```JavaScript
ac`"gift for you" for i of ${[1,2]}`
```

そんな稚拙な内包表記モドキでも見るだけ見てやるよ！という優しい方は続きを読んでいただけたら嬉しいです。

# タグ関数の振る舞い

先ほど述べたように、今回作る内包表記モドキはタグ関数として実装することにしました。
タグ関数といっても、実体は普通の関数です。
普通の関数なのですが、タグ関数として実行する場合は呼び出し方と渡される引数がやや特殊になります。

タグ関数は以下のようにテンプレートリテラルの前に置いて使われます。組み込みのものだと、`String.raw()`がタグ関数として機能します。

```JavaScript
String.raw`C:\foo\bar\baz`
```

タグ関数に渡される引数は少し変わった形式になります。
うまく言語化できないのですが、次のコードでなんとなく伝わるでしょうか。伝わってくれ。

```JavaScript
myFunc`aaa${111}bbb${222}ccc`
// 上のタグ関数としての呼び出しは、下のような引数で呼び出すのと基本的に等価
myFunc(["aaa", "bbb", "ccc"], 111, 222)
```

ですので、自分でタグ関数を作りたいときは、下段のような形式で引数が渡される[^1]ことを想定して関数を作っていけばOKです。

# 概形をつくる

次のコードが実行できるようになることを目標に概形を作ります。

```JavaScript
ac`i + j for i, j of ${[[1, 2], [2, 4], [3, 6]]} if (i + j) % 2`
```

上の呼び出しを普通の形式に置き換えると、


```JavaScript
ac(["i + j for i, j of ", " if (i + j) % 2"], [[1, 2], [2, 4], [3, 6]])
```

こうなります。

よって、外側は次のような感じでいいでしょう。


```JavaScript
function ac(strs, iter) { 
}
```

## 引数から`式` `変数名` `条件式`を取り出す

では、引数`strs`からユーザーが指定した`式` `変数名` `条件式`を取り出します。

まずは`式`と`変数名`から。
`式`と`変数名`は`strs[0]`から抽出します。forとof、及びその前後の空白を区切りとしてsplitしてしまえばいいでしょう。

```JavaScript
const [expr, vars] = strs[0].split(/ +for +| +of +/);
```

上で示した目標コードで言えば、`expr`には`"i + j"`、`vars`には`"i, j"`が入ります。
`変数名`は`["i", "j"]`のように一つずつ分けておきたいですね。さらにsplitして配列にします。

```JavaScript
const splitedVars = vars.split(/ *, */);
```

次に`strs[1]`から`条件式`を取り出します。
`strs[1].split(/ +if +/)[1]`で取り出すことはできるのですが、Pythonのリスト内包表記のようにif部分を省略できるようにしたいです。
よって、ifが無い場合は`"true"`を`条件式`とするようにします。


```JavaScript
const condition = strs[1].includes("if") ? strs[1].split(/ +if +/)[1] : "true";
```

とりあえず前準備としてはこんなもんでしょう。



## `条件式`を評価する関数と`式`を評価する関数を作る

先ほど`式` `変数名` `条件式`を取り出しましたが、これらはいずれも**文字列**です。
ただの文字列なので、そのままでは碌に評価することもできません。
一般に、文字列をJavaScriptコードとして評価するのには`eval()`やFunctionコンストラクタが使われます。

今回は、Functionコンストラクタを使って、`条件式`を評価する関数`judge`と`式`を評価する関数`getValue`を作ります。

Functionコンストラクタを使うのは初めてだったので、[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Function/Function)で構文を調べました。引用します。

>構文
new Function([arg1 [, arg2 [, ...argN]] ,] functionBody)
>引数
arg1, arg2, ... argN
    仮引数の名前として関数で用いるための名前。各々は、妥当な JavaScript の識別子と一致する文字列か、カンマで区切られた文字列などのリストでなければなりません。例えば、 "x", "theValue", "x,theValue" などです。
functionBody
    関数定義を形成する JavaScript の文を含む文字列。 

わかりやすい使用例も載っていました。
>// 二つの引数を取り、その合計を返す関数を生成します
>const adder = new Function('a', 'b', 'return a + b');
>
>// 関数を呼び出します
>adder(2, 6);
>// 8

なるほど、Functionコンストラクタ~~完全に~~理解した。

早速、`条件式`を評価する関数`judge`から作ります。

```JavaScript
const judge = new Function(...splitedVars, `return ${condition};`);
```

ちょっとわかりにくいでしょうか。
上で示した目標のコードで言えば、ちょうど以下のような感じで実行されると考えてください。

```JavaScript
const judge = new Function("i", "j", "return (i + j) % 2;");
```

同じ要領で、`式`を評価する関数`getValue`も作ります。

```JavaScript
const getValue = new Function(...splitedVars, `return ${expr};`);
```
さて、あとは空の配列を用意してループ内でpushして最後にreturnでいけそうです。

```JavaScript
const result = [];
// 色々と処理してresultにpushしていく
// …
// 最後にreturn
return result;
```

では、中の具体的な処理を書きます。

## returnする配列を作る処理を書く

for-of文で引数の`iter`をループして、先ほどの関数`getValue`で求めた値を配列`result`にpushしていけばいいでしょう。
ただし、pushするかどうかの条件判定を関数`judge`を用いて行うようにしておきます。

```JavaScript
for (const elem of iter) {
	if (judge(...elem)) {
		result.push(getValue(...elem));
	}
}
```



## 概形が完成

```JavaScript
function ac(strs, iter) {
	const [expr, vars] = strs[0].split(/ +for +| +of +/);
	const splitedVars = vars.split(/ *, */);
	const condition = strs[1].includes("if") ? strs[1].split(/ +if +/)[1] : "true";
	const judge = new Function(...splitedVars, `return ${condition};`);
	const getValue = new Function(...splitedVars, `return ${expr};`);
	const result = [];
	for (const elem of iter) {
		if (judge(...elem)) {
			result.push(getValue(...elem));
		}
	}	
	return result;
}
```

これで目標のコード↓が動きます。

```JavaScript
ac`i + j for i, j of ${[[1, 2], [2, 4], [3, 6]]} if (i + j) % 2`
```

**だがちょっと待って欲しい。**
今のままでは、次のような至極シンプルなコードが動きません。

```JavaScript
ac`i * 2 for i of ${[1, 2, 3]}`
```

これは、for-ofループ内で、イテラブルから取り出した要素をスプレッド構文で展開しようとしているのが原因です。
1や2などの数値はスプレッド構文で展開できないのでエラーになります。
もちろん数値だけでなく、文字列や真偽値でも同様です。

# 完成形

`変数名`が一つしか指定されていないときはスプレッド構文を使わないように条件分岐しましょう。
配列`splitedVars`のlengthで判定します。

```JavaScript
function ac(strs, iter) {
	const [expr, vars] = strs[0].split(/ +for +| +of +/);
	const splitedVars = vars.split(/ *, */);
	const condition = strs[1].includes("if") ? strs[1].split(/ +if +/)[1] : "true";
	const result = [];
	if (splitedVars.length === 1) {
		const judge = new Function(splitedVars[0], `return ${condition};`);
		const getValue = new Function(splitedVars[0], `return ${expr};`);
		for (const elem of iter) {
			if (judge(elem)) {
				result.push(getValue(elem));
			}
		}
	} else {
		const judge = new Function(...splitedVars, `return ${condition};`);
		const getValue = new Function(...splitedVars, `return ${expr};`);
		for (const elem of iter) {
			if (judge(...elem)) {
				result.push(getValue(...elem));
			}
		}
	}
	return result;
}
```

これで完成です！
先ほど動かなかったこれ↓も動きます。

```JavaScript
ac`i * 2 for i of ${[1, 2, 3]}`
```

やったね。

# おまけ

上でとりあえず完成させた配列内包表記ですが、色々と弱点や問題があります。
ここからは、それらを緩和しようとジタバタしてみます（解決するとは言ってない）。
また、オブジェクト内包表記やジェネレータ式なども作ったので最後の部分で紹介しています。

## ① ローカル関数が使えん

グローバルから参照できない関数が使えません。

```JavaScript
function main() {
	function add(a, b) {
		return a + b;
	}
	const it = [[1, 1], [2, 4] ,[3, 9], [4, 16]];
	console.log(ac`add(i, j) for i, j of ${it}`);
}

main();  // ReferenceError: add is not defined
```

仮に`main`の定義の中で`ac`を宣言したとしても同様です。
配列内包表記は内部でFunctionコンストラクタを使いますが、Functionコンストラクタで作られた関数はグローバルスコープで実行されるからです。

どうしましょう。使いたいローカル関数/オブジェクトの参照を`ac`に渡す必要がありそうです。
また、Functionコンストラクタでは文字列をベースに関数を作るので、関数/オブジェクト名も文字列として取得しておきたいです。

あれこれ考えた結果、名前の文字列と参照を同時に渡すにはES6で導入された[プロパティ定義のショートハンド](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Object_initializer#new_notations_in_ecmascript_2015)がいいんじゃないかという結論に達しました。

```JavaScript
const a = function() {console.log("a");};
const b = function() {console.log("b");}; 
const obj = {a, b};  // {a: a, b: b}のショートハンド
console.log(Object.keys(obj));  // Array [ "a", "b" ]
console.log(Object.values(obj));  // Array [ function a(), function b() ]
```

特に`Object.keys()`で名前が取得できるのが強いです。

というわけで、以下のような構文でローカルオブジェクトを使えるようにしました。

```JavaScript
ac`式 for 変数名 of ${イテラブルオブジェクト} if 条件式 locals ${{ローカルオブジェクト}}`
```

実装は以下。

```JavaScript
function ac(strs, iter, locals = {}) {
	const [expr, vars] = strs[0].split(/ +for +| +of +/);
	const splitedVars = vars.split(/ *, */);
	const condition = strs[1].includes("if") ? strs[1].split(/ +if +| +locals +/)[1] : "true";
	const localsName = Object.keys(locals);
	const localsRef = Object.values(locals);
	const result = [];
	if (splitedVars.length === 1) {
		const judge = new Function(...localsName, splitedVars[0], `return ${condition};`);
		const getValue = new Function(...localsName, splitedVars[0], `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, elem)) {
				result.push(getValue(...localsRef, elem));
			}
		}
	} else {
		const judge = new Function(...localsName, ...splitedVars, `return ${condition};`);
		const getValue = new Function(...localsName, ...splitedVars, `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, ...elem)) {
				result.push(getValue(...localsRef, ...elem));
			}
		}
	}
	return result;
}
```

使用例です。

```JavaScript
function main() {
	const myObj = {
		add(a, b) {
			return a + b;
		}
	}
	function isOdd(x) {
		return x % 2 === 1;
	}
	const it = [[1, 1], [2, 4] ,[3, 9], [4, 16]];
	console.log(ac`myObj.add(i, j) for i, j of ${it} if isOdd(i) locals ${{myObj, isOdd}}`);
}

main()
```

…もう普通に`filter()`と`map()`併用するほうが楽なのでは…？

## ② "gift for you"←区切りのforと区別できん

冒頭でも述べたアレです。

```JavaScript
ac`"gift for you" for i of ${[1,2]}`
```

`式`と`変数名`を抽出するときに、文字列`"gift for you"`の`" for "`の部分でもsplitしてしまうのでうまく動かなくなります。

これは…どうにもなりませんでした。
構文解析ができればいいんですが、そんなスキルはないし、ちょっとした遊びのつもりなのに大仰すぎます。

そうです、そもそもかっこつけてタグ関数なんて使おうとしたのが良くないのです。
普通の関数で実装すればこんな問題おさらばです。

```JavaScript
ac(式, 変数名, イテラブルオブジェクト, 条件式 , {ローカルオブジェクト})
```

ちょっと引数の順序が気持ち悪い気はしますが、この形式で十分じゃありませんか？

こんな感じの実装にすればいいです。

```JavaScript
function ac(expr, vars, iter, condition = "true", locals = {}) {
	const splitedVars = vars.split(/ *, */);
	const localsName = Object.keys(locals);
	const localsRef = Object.values(locals);
	const result = [];
	if (splitedVars.length === 1) {
		const judge = new Function(...localsName, splitedVars[0], `return ${condition};`);
		const getValue = new Function(...localsName, splitedVars[0], `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, elem)) {
				result.push(getValue(...localsRef, elem));
			}
		}
	} else {
		const judge = new Function(...localsName, ...splitedVars, `return ${condition};`);
		const getValue = new Function(...localsName, ...splitedVars, `return ${expr};`);
		for (const elem of iter) {
			if (judge( ...localsRef, ...elem)) {
				result.push(getValue(...localsRef, ...elem));
			}
		}
	}
	return result;
}

```

さっき動かなかったアレも実行できます。

```JavaScript
ac("'gift for you'", "i" , [1,2])
```

…やっぱり普通に`filter()`と`map()`併用するほうが楽なのでは…？

## ③ その他の○○内包表記 & ジェネレータ式

### オブジェクト内包表記

```JavaScript
function oc(strs, iter, locals = {}) {
	const [expr, vars] = strs[0].split(/ +for +| +of +/);
	const splitedVars = vars.split(/ *, */);
	const condition = strs[1].includes("if") ? strs[1].split(/ +if +| +locals +/)[1] : "true";
	const localsName = Object.keys(locals);
	const localsRef = Object.values(locals);
	const result = {};
	if (splitedVars.length === 1) {
		const judge = new Function(...localsName, splitedVars[0], `return ${condition};`);
		const getEntry = new Function(...localsName, splitedVars[0], `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, elem)) {
				const [key, value] = getEntry(...localsRef, elem);
				result[key] = value;
			}
		}
	} else {
		const judge = new Function(...localsName, ...splitedVars, `return ${condition};`);
		const getEntry = new Function(...localsName, ...splitedVars, `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, ...elem)) {
				const [key, value] = getEntry(...localsRef, ...elem);
				result[key] = value;
			}
		}
	}
	return result;
}
```

#### 構文

```JavaScript
oc`[キー, 値] for 変数名 of ${イテラブルオブジェクト} if 条件式 locals ${{ローカルオブジェクト}}`;
```

#### 使用例

```JavaScript
console.log(oc`[j, i] for i, j of ${[[1, "a"], [2, "b"]]}`);
// Object { a: 1, b: 2 }
```

### ジェネレータ式

```JavaScript
function ge(strs, iter, locals = {}) {
	const [expr, vars] = strs[0].split(/ +for +| +of +/);
	const splitedVars = vars.split(/ *, */);
	const condition = strs[1].includes("if") ? strs[1].split(/ +if +| +locals +/)[1] : "true";
	const localsName = Object.keys(locals);
	const localsRef = Object.values(locals);
	let result;
	if (splitedVars.length === 1) {
		const judge = new Function(...localsName, splitedVars[0], `return ${condition};`);
		const getValue = new Function(...localsName, splitedVars[0], `return ${expr};`);
		result = function*() {
			for (const elem of iter) {
				if (judge(...localsRef, elem)) {
					yield getValue(...localsRef, elem);
				}
			}
		}
	} else {
		const judge = new Function(...localsName, ...splitedVars, `return ${condition};`);
		const getValue = new Function(...localsName, ...splitedVars, `return ${expr};`);
		result = function*() {
			for (const elem of iter) {
				if (judge(...localsRef, ...elem)) {
					yield getValue(...localsRef, ...elem);
				}
			}
		}
	}
	return result();
}
```

#### 構文

```JavaScript
ge`式 for 変数名 of ${イテラブルオブジェクト} if 条件式 locals ${{ローカルオブジェクト}}`;
```

#### 使用例

```JavaScript
for (const i of ge`i + 2 for i of ${[1, 2, 3]}`) {
  console.log(i);
}
// 3
// 4
// 5
```

### Map内包表記


```JavaScript
function mc(strs, iter, locals = {}) {
	const [expr, vars] = strs[0].split(/ +for +| +of +/);
	const splitedVars = vars.split(/ *, */);
	const condition = strs[1].includes("if") ? strs[1].split(/ +if +| +locals +/)[1] : "true";
	const localsName = Object.keys(locals);
	const localsRef = Object.values(locals);
	const result = new Map();
	if (splitedVars.length === 1) {
		const judge = new Function(...localsName, splitedVars[0], `return ${condition};`);
		const getEntry = new Function(...localsName, splitedVars[0], `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, elem)) {
				result.set(...getEntry(...localsRef, elem));
			}
		}
	} else {
		const judge = new Function(...localsName, ...splitedVars, `return ${condition};`);
		const getEntry = new Function(...localsName, ...splitedVars, `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, ...elem)) {
				result.set(...getEntry(...localsRef, ...elem));
			}
		}
	}
	return result;
}
```

#### 構文

```JavaScript
mc`[キー, 値] for 変数名 of ${イテラブルオブジェクト} if 条件式 locals ${{ローカルオブジェクト}}`;
```

#### 使用例

```JavaScript
mc`[i, (j+2)**2] for i, j of ${[["a", 1], ["b", 2]]}`;
// Map { a → 9, b → 16 }
```

なお、`const result = new Map();`を`const result = new WeakMap();`にするとWeakMap内包表記になります。

### Set内包表記

```Javascript
function sc(strs, iter, locals = {}) {
	const [expr, vars] = strs[0].split(/ +for +| +of +/);
	const splitedVars = vars.split(/ *, */);
	const condition = strs[1].includes("if") ? strs[1].split(/ +if +| +locals +/)[1] : "true";
	const localsName = Object.keys(locals);
	const localsRef = Object.values(locals);
	const result = new Set();
	if (splitedVars.length === 1) {
		const judge = new Function(...localsName, splitedVars[0], `return ${condition};`);
		const getValue = new Function(...localsName, splitedVars[0], `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, elem)) {
				result.add(getValue(...localsRef, elem));
			}
		}
	} else {
		const judge = new Function(...localsName, ...splitedVars, `return ${condition};`);
		const getValue = new Function(...localsName, ...splitedVars, `return ${expr};`);
		for (const elem of iter) {
			if (judge(...localsRef, ...elem)) {
				result.add(getValue(...localsRef, ...elem));
			}
		}
	}
	return result;
}
```

#### 構文

```JavaScript
sc`式 for 変数名 of ${イテラブルオブジェクト} if 条件式 locals ${{ローカルオブジェクト}}`;
```

#### 使用例

```JavaScript
sc`(i+2)**2 for i of ${[1, 2, 3]}`
// Set(3) [ 9, 16, 25 ]
```

なお、`const result = new Set();`を`const result = new WeakSet();`にするとWeakSet内包表記になります。

[^1]: タグ関数の第一引数に渡される配列にはrawというプロパティが追加されているので、厳密にいえば等価ではありません。ただ、今回はrawプロパティを使わないのでスルーで。