LoginSignup
0
1

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-03-21

前の記事の続きです。ここから読むとほとんど意味不明です。わからないところは適宜戻って読んでいただくか、コメントで質問していただけるとありがたいです。
ざっくり言うと、既に確定した配列にmapやfilterするのではなく、逆にまだ確定してない何かにmapやfilterしたいし、その結果を条件で区切って配列にしたい、という人(っていうかつまり自分)のための工夫です。

こんな風に書けるようになったけど...

例によって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)
//あるいは
dot(spreadG, attachG(console.log), passTimesG(15), applyG(fizzBuzz), nestG(n=>n+1))(1)

//=>
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

実際使ってみると...

  • ジェネレーターと配列用の関数を混ぜて並べてしまってエラー出しまくり。
  • spreadGうざい。

混ぜて使えるようにしたのが、かえって混乱の元だったようで。

こうなればいいのかな?

spreadGを関数内に隠蔽して

  • pipeG, dotG : ジェネレーター専用。ジェネレーターを合成し配列へ展開する
  • pipe, dot : 普通の関数を合成する

をわけてみました。

const fizzBuzz = x =>
 x % 3 === 0 && x % 5 === 0 ? "FizzBuzz"
 : x % 3 === 0 ? "Fizz"
 : x % 5 === 0 ? "Buzz"
 : x
;

pipeG(1)(nestG(n=>n+1), applyG(fizzBuzz), passTimesG(15), attachG(console.log))
//あるいは
dotG(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 ){
    while( true ) {
      yield x
      x = f( x )
    }
  }
}
// nestGの初期値を二つとる版。
const nest2G = f => x =>function*( y ){ 
  yield x 
  yield y 
  while(true){ 
    yield x = f(x)(y); 
    [x, y]= [y, x] 
  } 
}
//ジェネレーター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 pipeG = x => (...f) =>{
  const spreadG = xs => [...xs]
  const fs =[...f, spreadG]
  return fs.reduce( (acc, e) => e(acc), x )
}
const dotG = (...f) => x => {
  const spreadG = xs => [...xs]
  const fs = [spreadG,...f]
  return fs.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個とる
pipeG(0)( nestG(n=>n+1), passTimesG(10) )
//=> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
// 普通の関数と合成。pipeGの結果をpipeの第一引数(初期値)としてセットします。
pipe( pipeG(0)(nestG(n=>n+1), passTimesG(10)) )
    ( filterC(e=>e%2===0), forEachC(e=>console.log(e)) )
/*=>
0
2
4
6
8
*/
// pipeGの結果は配列なので、配列のメソッドが使えます。
pipeG(0)( nestG(n=>n+1),passTimesG(10) )
    .filter(e=>e%2===0).forEach(e=>console.log(e))
/*=>
0
2
4
6
8
*/
// 無限の等差数列から10個とる
dotG( passTimesG(10), nestG(n=>n+1) )(0)
//=> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
// 普通の関数と合成。dotGの結果をdotの第二引数(初期値)としてセットします。
dot( 
    forEachC(e=>console.log(e))
    , filterC(e=>e%2===0) 
)( dotG( passTimesG(10), nestG(n=>n+1) )(0) )
/*=>
0
2
4
6
8
*/
// 普通の関数と合成。dotGは第二引数を与えなければ普通の関数なのでdot内で使える。
dot( 
    forEachC(e=>console.log(e))
    , filterC(e=>e%2===0)
    , dotG( passTimesG(10), nestG(n=>n+1) )
)(0)
/*=>
0
2
4
6
8
*/

ちょっと使ってみて...

あいかわらず、混ぜるな危険!であることには違いないのですが、ジェネレーター界と配列界を分けられるので、間違いに気がつくようにはなりました。

実際の使用例として、前に書いた記事をこれを使って書き直してみました。
Javascript:Simulaの試着室シミュレーションをやってみる
唐揚げつまんでみた

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