3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ECMAScript 2015 で畳み込む話

Last updated at Posted at 2018-09-05

経緯

Qiita ML からこの記事を読む。

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

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

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

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 じゃないですかー、からの

これはもう畳むしか

右から

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)))))))))

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

左から

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)

うむ、合成、合成。

そのほかリスト操作

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)

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を実装するなら何でも良いっぽい。

  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
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% を拡張してみる。

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

つまりこう!

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。

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かどうか判定するのが正道となるようです。

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を使って畳み込まない実装にするのが良さげ。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?