経緯
Qiita ML からこの記事を読む。
ライブラリに振り回される以前に触る機会がなくなっています
ECMAScript2015とかあったのかー、からの
アロー関数可愛いよアロー関数
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
うむ。キモい。
さらっと流されている分割代入(デストラクチャリング)が気になる
のでざっと言語仕様を確認。
- http://www.ecma-international.org/ecma-262/6.0/#sec-primary-expression
- http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-assignment
- http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-binding-patterns
- 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 じゃないですかー、からの
これはもう畳むしか
右から
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)))))))))
うむ、帰納再起、帰納再起。
左から
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)
うむ、合成、合成。
そのほかリスト操作
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)
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-runtime-semantics-iteratordestructuringassignmentevaluation
- http://www.ecma-international.org/ecma-262/6.0/#sec-destructuring-binding-patterns-runtime-semantics-iteratorbindinginitialization
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% を拡張してみる。
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
つまりこう!
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。
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かどうか判定するのが正道となるようです。
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を使って畳み込まない実装にするのが良さげ。