LoginSignup
1
2

More than 5 years have passed since last update.

JavaScript:関数を無限iterate して map して filter して take とかできるジェネレーターを考えてみた 2

Last updated at Posted at 2018-03-20

前の記事ではあまりに意味不明の構文に見えるので、もうすこし見やすいようにしてみました。
だいぶいじりましたが主旨は同じです。

想定読者 : こう書けるとうれしい人

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
1
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
1
2