前の記事ではあまりに意味不明の構文に見えるので、もうすこし見やすいようにしてみました。
だいぶいじりましたが主旨は同じです。
想定読者 : こう書けるとうれしい人
FizzBuzzです。パイプライン演算子の雰囲気です。
const fizzBuzz = x =>
x % 3 === 0 && x % 5 === 0 ? "FizzBuzz"
: x % 3 === 0 ? "Fizz"
: x % 5 === 0 ? "Buzz"
: x
;
pipe(1)(nestG(n=>n+1), applyG(fizzBuzz), passTimesG(15), attachG(console.log), spreadG)
//=>こんな結果になればいいな。
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
あるいはこっち。関数合成の雰囲気です。
dot(spreadG, attachG(console.log), passTimesG(15), applyG(fizzBuzz), nestG(n=>n+1))(1)
もはや何言語だかわからなくなりそうですが、こうなるとうれしいー!っていうだいぶ変わった人向けです。
これを読みこめばOK
それぞれ使う関数をコピペするか、エクスポート/インポートするかすれば使えます。
//関数fをくりかえしかけて、その結果を配列にします。[...]が展開してくれるんで使いどころないかも。
function iterateG(f){
return function*(xs){
let yss = []
let ys = xs
while( true ) {
yss = yss.concat( [ys] )
yield yss
ys = f( ys )
}
}
}
//関数fを繰り返しかけて結果を返します。[... ] と組み合わせてiterate相当。
function nestG( f ) {
return function*( x ){
let y = x
while( true ) {
yield y
y = f( y )
}
}
}
//ジェネレーターgを呼び出し値に関数fをかけます。
//[... ] と組み合わせてmap相当。
function applyG( f ) {
return function*( g ){
let x = g.next();
while( !x.done ){
yield f( x.value );
x = g.next();
}
}
}
// 内部値の初期値をaccとして、2引数関数fに内部値とgの値を適用し、結果を内部値とし、返す。
//reduce相当。[... ] と組み合わせてscan相当。
function accumG( f ){
return function( acc ){
return function*( g ){
let x = g.next()
let y = acc
while( !x.done ){
yield y = f( y, x.value )
x = g.next()
}
}
}
}
//gの値を判定関数fで調べ、真なら値を返します。偽なら値を返さず動作を継続。
//[... ] と組み合わせてfilter相当。
function passIfG( f ) {
return function*( g ){
let x = g.next();
while( !x.done ) {
if( f(x.value) ) yield x.value;
x = g.next();
}
}
}
// gの値をn個ブロックします。[... ] と組み合わせてdrop相当。
function blockTimesG( n ){
return function*( g ){
let x
for(let i = 0; i < n; i++){
x = g.next();
if( x.done ) return;
}
x = g.next();
while( !x.done ){
yield x.value
x = g.next();
}
}
}
//gの値をn個とりだします。とりだし終ったら終了メッセージを出す。
//[... ] と組み合わせてtake相当。
function passTimesG( n ){
return function*( g ){
let x
for(let i = 0; i<n; i++){
x = g.next();
if( x.done ) return;
yield x.value;
}
}
}
//gの値をfで調べ、真の間だけ値を返します。偽になったら終了メッセージを出す。
//[... ] と組み合わせてtakeWhile相当。
function passWhileG( f ){
return function*( g ){
let x = g.next()
while( !x.done && f( x.value ) ) {
yield x.value;
x = g.next();
}
}
}
// gの値をfに適用して捨て、gの値をそのまま出力する。fの副作用が必要なとき使う。
function attachG( f ) {
return function*( g ){
let x= g.next();
while( !x.done ) {
f(x.value);
yield x.value;
x = g.next();
}
}
}
//関数合成のための関数
const pipe = x => (...f) => f.reduce( (acc, e) => e(acc), x )
const dot = (...f) => x => f.reduceRight( (acc, e) => e(acc), x )
//スプレッド構文、メソッドを関数にしておく
const spreadG = xs => [...xs]
const sortC = f => xs => xs.sort( f )
const mapC = f => xs => xs.map( f )
const filterC = f => xs => xs.filter( f )
const forEachC = f => xs => xs.forEach( f )
使用例:
// 無限の等差数列から10個とる。
pipe(0)(nestG(n=>n+1), passTimesG(10), spreadG)
=> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
// 等差数列に階乗を適用して10個とる。
const fact = n => n<=0 ? 1 : n*fact(n-1));
pipe(0)(nestG(n=>n+1),applyG(fact, passTimesG(10), spreadG)
=> [ 1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880 ]
// passWhileGを使って10000未満の階乗。
pipe(0)(nestG(n=>n+1),applyG(fact, passWhileG(e=>e<10000), spreadG)
=> [ 1, 1, 2, 6, 24, 120, 720, 5040 ]
//10000未満の階乗のうち7で割り切れるもの。
pipe(0)(nestG(n=>n+1),applyG(fact, passWhileG(e=>e<10000), passIfG(e=>e%7==0), spreadG)
=> [ 5040 ]
// 上に同じ。applyGとpassWhileGの間で途中経過をコンソール出力。
pipe(0)(nestG(n=>n+1),applyG(fact, attachG(console.log), passWhileG(e=>e<10000), passIfG(e=>e%7==0), spreadG)
/* コンソール出力
1
1
2
6
24
120
720
5040
40320//これを passWhileGがブロックし、passIfGに終了のメッセージを送る
*/
=> [ 5040 ]
使い方 :
- 関数合成の仕組みを使っていますが、やってることは要素を一個ずつ処理して並べていくいわば大きなwhileループです。spreadGが適用されるまでは配列ですらありません。この辺りは前の記事と同じです。適宜、読み替えてください。
- spreadGが適用された後は、pipe、dotの外側ですが普通の配列のメソッドが使えます。
- 使いそうな配列メソッドを関数にしたバージョン(sortC,mapCなど)を最後に付けておきました。これらはspreadGが適用された後、pipe、dotの内側で使えます。
一番使いそうな場面は、確定した配列にmapとかfilterするのではなく、逆に何かまだ確定してないものにmapとかfilterしてその結果をある条件で区切って配列にしたい、というような場合でしょうか。
たとえばこんなことができます。
フィボナッチ数列のうち奇数を1000未満までの配列にしてFizzBuzzするとか。
const fizzBuzz = x =>
x % 3 === 0 && x % 5 === 0 ? "FizzBuzz"
: x % 3 === 0 ? "Fizz"
: x % 5 === 0 ? "Buzz"
: x
;
// 初期値を二つとるジェネレーター。nestGをちょっと変形したもの。
const nest2G = f => x =>function*( y ){
yield x
yield y
while(true){
yield x = f(x)(y);
[x, y] = [y, x]
}
}
// フィボナッチ数列のもと。前二つを足すと次の要素になる。
const fibo = x => y => x + y;
pipe(1)(
nest2G(fibo)(0)
, passIfG(e=>e%2===1)
, passWhileG(n=>n<1000)
, applyG(fizzBuzz)
, spreadG
, forEachC(e=>console.log(e))
)
//=>
1
1
Fizz
Buzz
13
Fizz
Buzz
89
233
377
Fizz