前の記事の続きです。ここから読むとほとんど意味不明です。わからないところは適宜戻って読んでいただくか、コメントで質問していただけるとありがたいです。
ざっくり言うと、既に確定した配列に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の試着室シミュレーションをやってみる
唐揚げつまんでみた