ECMAScript 2015 で畳み込む話

# 経緯

Qiita ML から[この記事](https://qiita.com/hakozaru/items/c00e472ab0f5e823098c)を読む。

## ライブラリに振り回される以前に触る機会がなくなっています

ECMAScript2015とかあったのかー、からの

## アロー関数可愛いよアロー関数

```javascript:scope.js
const i = new (class {
    constructor(name) {
        [ this.name
        , this.getField
        , this.getScope
        , this.setScope
        ] = [name, () => this.name, () => name, value => name = value];
    }
})("name");

console.log(i.getField(),i.getScope());
i.name = "foo";
console.log(i.getField(),i.getScope());
i.setScope("bar");
console.log(i.getField(),i.getScope());
```

```
> node scope.js
name name
foo name
foo bar
```

うむ。キモい。

## さらっと流されている分割代入（デストラクチャリング）が気になる

のでざっと言語仕様を確認。


1. http://www.ecma-international.org/ecma-262/6.0/#sec-primary-expression
2. http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-assignment
3. http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-binding-patterns
4. http://www.ecma-international.org/ecma-262/6.0/#sec-array-initializer

んんんん AssignmentRestElement？

AssignmentRestElement[Yield] :
... DestructuringAssignmentTarget[?Yield]

むむむむ SpreadElement？

SpreadElement[Yield] :
... AssignmentExpression[In, ?Yield]

これ cons と uncons じゃないですかー、からの

# これはもう畳むしか

## [右から](http://hackage.haskell.org/package/base-4.11.1.0/docs/Prelude.html#v:foldr)

```javascript:foldr.js
const op = (x,y) => `f(${x},${y})`
const foldr = (f,z,x,...xs) =>
    x === undefined ? z : f(x, foldr(f,z,...xs));
exports.foldr = foldr;
if(require.main === module) {
    console.log(foldr(op,"z",1,2,3,4,5,6,7,8,9));
}
```

```
> node foldr.js
f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,z)))))))))
```

うむ、帰納再起、帰納再起。


## [左から](http://hackage.haskell.org/package/base-4.11.1.0/docs/Prelude.html#v:foldl)

```javascript:foldl.js
const {foldr} = require('./foldr.js');
const op = (x,y) => `f(${x},${y})`
const foldl = (f,a,x,...xs) =>
    foldr((x1,g) => x0 => g(f(x0, x1)), v => v, x, ...xs)(a);
if(require.main === module) {
    console.log(foldl(op,"a",0,1,2,3,4,5,6,7,8,9));
}
```

```
> node foldl.js
f(f(f(f(f(f(f(f(f(f(a,0),1),2),3),4),5),6),7),8),9)
```

うむ、合成、合成。

## [そのほかリスト操作](http://hackage.haskell.org/package/base-4.11.1.0/docs/Prelude.html#g:13)

```javascript:listop.js
const {foldr} = require('./foldr.js');
const map = (f, ...s) =>
    foldr((x,xs) => [f(x),...xs], [], ...s);
const filter = (f, ...s) =>
    foldr((x,xs) => f(x) ? [x,...xs] : xs, [], ...s);
const reverse = (...s) =>
    foldr((x,xs) => [...xs,x],[],...s);

console.log(map(x => x + 10,1,2,3,4,5,6,7,8,9));
console.log(filter(x => x % 2,1,2,3,4,5,6,7,8,9));
console.log(reverse(1,2,3,4,5,6,7,8,9));
```

```
> node listop.js
[ 11, 12, 13, 14, 15, 16, 17, 18, 19 ]
[ 1, 3, 5, 7, 9 ]
[ 9, 8, 7, 6, 5, 4, 3, 2, 1 ]
```

うむ、定番、定番。

## [リスト構築(Scans)](http://hackage.haskell.org/package/base-4.11.1.0/docs/Prelude.html#g:16)

```javascript:scanop.js
const op = (x,y) => `f(${x},${y})`;
const scanr = (f,z,x,...xs) =>
    x === undefined ? [z] : ((y,...yz) => [f(x,y),y,...yz])(...scanr(f,z,...xs));
for(s of scanr(op,"a",1,2,3,4,5,6,7,8,9)) { console.log(s); }

const scanl = (f,z,x,...xs) =>
    x === undefined ? [z] : [z,...scanl(f,f(z,x),...xs)];
for(s of scanl(op,"a",1,2,3,4,5,6,7,8,9)) { console.log(s); }
```

```
> node scan.js
f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,a)))))))))
f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,a))))))))
f(3,f(4,f(5,f(6,f(7,f(8,f(9,a)))))))
f(4,f(5,f(6,f(7,f(8,f(9,a))))))
f(5,f(6,f(7,f(8,f(9,a)))))
f(6,f(7,f(8,f(9,a))))
f(7,f(8,f(9,a)))
f(8,f(9,a))
f(9,a)
a
a
f(a,1)
f(f(a,1),2)
f(f(f(a,1),2),3)
f(f(f(f(a,1),2),3),4)
f(f(f(f(f(a,1),2),3),4),5)
f(f(f(f(f(f(a,1),2),3),4),5),6)
f(f(f(f(f(f(f(a,1),2),3),4),5),6),7)
f(f(f(f(f(f(f(f(a,1),2),3),4),5),6),7),8)
f(f(f(f(f(f(f(f(f(a,1),2),3),4),5),6),7),8),9)
```

漲ってきたー（ぐるぐる目）。

## アロー関数可愛いよアロー関数（ふたたび）

配列記法の分割代入・束縛は[Iterable](http://www.ecma-international.org/ecma-262/6.0/#sec-iterable-interface)を実装するなら何でも良いっぽい。

1. http://www.ecma-international.org/ecma-262/6.0/#sec-runtime-semantics-iteratordestructuringassignmentevaluation
2. http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization

```javascript:currying.js
const foldr = f => z => (...s) => !s.length ? z :
    ((x,...xs) => f(x)(foldr(f)(z)(...xs)))(...s);
const foldl = f => z => (...s) => !s.length ? z :
    ((x,...xs) => foldl(f)(f(z)(x))(...xs))(...s);
const scanr = f => z => (...s) => !s.length ? [z] :
    ((x,...xs) => ((y,...ys) => [f(x)(y),y,...ys])(...scanr(f)(z)(...xs)))(...s);
const scanl = f => z => (...s) => !s.length ? [z] :
    ((x,...xs) => ((y,...ys) => [z,y,...ys])(...scanl(f)(f(z)(x))(...xs)))(...s);
const op = x => y => `f(${x},${y})`;
const g = function *(i) { while(i < 10) yield i++; };
for(var f of [foldr, foldl, scanr, scanl]) {
    console.log(f(op)("z")(undefined,...g(0)));
}
```

カリー化してみたらピリオドの自重してない感じがとても素敵です。要素中にundefinedが入っていると畳み込みが中断してしまうバグもIIFE`((x,...xs){})(s...)`を使って可愛く修正。

```
> node currying.js
f(undefined,f(0,f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,z)))))))))))
f(f(f(f(f(f(f(f(f(f(f(z,undefined),0),1),2),3),4),5),6),7),8),9)
[ 'f(undefined,f(0,f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,z)))))))))))',
  'f(0,f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,z))))))))))',
  'f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,z)))))))))',
  'f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,z))))))))',
  'f(3,f(4,f(5,f(6,f(7,f(8,f(9,z)))))))',
  'f(4,f(5,f(6,f(7,f(8,f(9,z))))))',
  'f(5,f(6,f(7,f(8,f(9,z)))))',
  'f(6,f(7,f(8,f(9,z))))',
  'f(7,f(8,f(9,z)))',
  'f(8,f(9,z))',
  'f(9,z)',
  'z' ]
[ 'z',
  'f(z,undefined)',
  'f(f(z,undefined),0)',
  'f(f(f(z,undefined),0),1)',
  'f(f(f(f(z,undefined),0),1),2)',
  'f(f(f(f(f(z,undefined),0),1),2),3)',
  'f(f(f(f(f(f(z,undefined),0),1),2),3),4)',
  'f(f(f(f(f(f(f(z,undefined),0),1),2),3),4),5)',
  'f(f(f(f(f(f(f(f(z,undefined),0),1),2),3),4),5),6)',
  'f(f(f(f(f(f(f(f(f(z,undefined),0),1),2),3),4),5),6),7)',
  'f(f(f(f(f(f(f(f(f(f(z,undefined),0),1),2),3),4),5),6),7),8)',
  'f(f(f(f(f(f(f(f(f(f(f(z,undefined),0),1),2),3),4),5),6),7),8),9)' ]
```

# では拡張

[%IteratorPrototype%](http://www.ecma-international.org/ecma-262/6.0/#sec-%iteratorprototype%-object) を拡張してみる。

```javascript:ng1.js
const foldr = f => z => (...s) => !s.length ? z :
    ((x,...xs) => f(x)(foldr(f)(z)(...xs)))(...s);
Object.assign(Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())), {
    foldr: function(f) { return z => foldr(f)(z)(...this) },
});
const op = x => y => `f(${x},${y})`;
console.log([0,1,2,3,4,5].foldr(op)("z"));
```

## 駄目でした

```
> node ng1.js
ng1.js:7
console.log([0,1,2,3,4,5].foldr(op)("z"));
                          ^

TypeError: [0,1,2,3,4,5].foldr is not a function
```

## Iterable ≠ Iterator

つまりこう！

```javascript:ng2.js
const foldr = f => z => (...s) => !s.length ? z :
    ((x,...xs) => f(x)(foldr(f)(z)(...xs)))(...s);
Object.assign(Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())), {
    foldr: function(f) { return z => foldr(f)(z)(...this) },
});
const op = x => y => `f(${x},${y})`;
console.log([0,1,2,3,4,5][Symbol.iterator]().foldr(op)("z"));
```

```
> node ng2.js
f(0,f(1,f(2,f(3,f(4,f(5,z))))))
```

コレジャナイ感がハンパないです。

## Generator = Iterable = Iterator

なのでこれだけはOK。

```javascript:ok.js
const foldr = f => z => (...s) => !s.length ? z :
    ((x,...xs) => f(x)(foldr(f)(z)(...xs)))(...s);
Object.assign(Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())), {
    foldr: function(f) { return z => foldr(f)(z)(...this) },
});
const g = function *(i) { while(i < 10) yield i++; };
const op = x => y => `f(${x},${y})`;
console.log(g(0).foldr(op)("z"));
```

```
> node ok.js
f(0,f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,z))))))))))
```

## IterableのPrototypeは存在しない

this[Symbol.iterator]の有無でIterableかどうか判定するのが正道となるようです。

```javascript:complete.js
const foldr = f => (...s) => !s.length ? z => z :
    ((x,...xs) => z => f(x)(foldr(f)(...xs)(z)))(...s);
const foldl = f => (...s) => !s.length ? z => z :
    ((x,...xs) => z => foldl(f)(...xs)(f(z)(x)))(...s);
const scanr = f => (...s) => !s.length ? z => [z] :
    ((x,...xs) => z => ((y,...ys) => [f(x)(y),y,...ys])(...scanr(f)(...xs)(z)))(...s);
const scanl = f => (...s) => !s.length ? z => [z] :
    ((x,...xs) => z => ((y,...ys) => [z,y,...ys])(...scanl(f)(...xs)(f(z)(x))))(...s);
Object.assign(Object.prototype, {
    foldr: function(f) { return this[Symbol.iterator] ? foldr(f)(...this) : z => z },
    foldl: function(f) { return this[Symbol.iterator] ? foldl(f)(...this) : z => z },
    scanr: function(f) { return this[Symbol.iterator] ? scanr(f)(...this) : z => [z] },
    scanl: function(f) { return this[Symbol.iterator] ? scanl(f)(...this) : z => [z] },
});
const op = x => y => `f(${x},${y})`;
const g = function *(i) { while(i < 10) yield i++; };
console.log(g(0).foldr(op)("z"));
console.log([0,1,2,3,4,5,6,7,8,9].foldl(op)("z"));
console.log("0123456789".scanr(x => y => [x,...y])([]));
console.log(Object.keys({foo: 0, bar:1, baz:2}).scanl(x => y => [...x,y])([]));
```

```
> node complete.js
f(0,f(1,f(2,f(3,f(4,f(5,f(6,f(7,f(8,f(9,z))))))))))
f(f(f(f(f(f(f(f(f(f(z,0),1),2),3),4),5),6),7),8),9)
[ [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' ],
  [ '1', '2', '3', '4', '5', '6', '7', '8', '9' ],
  [ '2', '3', '4', '5', '6', '7', '8', '9' ],
  [ '3', '4', '5', '6', '7', '8', '9' ],
  [ '4', '5', '6', '7', '8', '9' ],
  [ '5', '6', '7', '8', '9' ],
  [ '6', '7', '8', '9' ],
  [ '7', '8', '9' ],
  [ '8', '9' ],
  [ '9' ],
  [] ]
[ [], [ 'foo' ], [ 'foo', 'bar' ], [ 'foo', 'bar', 'baz' ] ]
```

mapやfilterなどのリスト操作はGeneratorを使って畳み込まない実装にするのが良さげ。