Functional JavaScript


Conception
1-class関数(一級関数)
価格は
  • .
  • 変数に含めることができます.
  • 関数のパラメータとして使用できます.
  • 関数の結果として使用できます.
  • Higher-order関数(高次関数)
    高次関数の2種類
    1.パラメータとして関数を実行する関数を受信する(callback)
    2.関数を作成して返される関数(モジュールを作成して返される関数)
    const users = [
      	{ "age": 16, "name": "Dalton" },
    	{ "age": 20, "name": "Amal" },
    	{ "age": 34, "name": "Owen" },
    	{ "age": 42, "name": "Vincent" },
    	{ "age": 56, "name": "Kermit" }
    ]
    functions
    map
  • def
  • const map = (f, iter) => {
      const res = [];
      for (const el of iter) res.push(f(el));
    
      return res;
    }
  • usage
  •  map(u => u.name, users);
    filter
  • def
  • const filter = (f, iter) => {
      const res = [];
      for (const el of iter) if (f(el)) res.push(el);
      
      return res;
    }
  • usage
  • filter(u => u.age < 42, users);
    reduce
  • def
  • const reduce = (f, acc, iter) => {
      if (!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
      }
      for (const el of iter) acc = f(acc, el);
      
      return acc;
    }
  • usage
  • reduce((total_age, u) => total_age + u.age, 0, users)
    reduce((total_age, u) => total_age + u.age, users)
    go
  • def
    関数
  • を順次実行
    const go = (...args) => reduce((a, f) => f(a), args);
  • usage
  • go(users,
       users => filter(u => u.age < 42, users),
       users => map(u => u.age, users),
       ages => reduce((a, b) => a + b, ages),
       console.log);
    pipe
  • def
    関数を順番に実行する関数を返します.
  • const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);
  • usage
  • const totalYoungAge = pipe(users => filter(u => u.age < 20, users),
                               users => map(u => u.age, users),
                               ages => reduce((a, b) => a + b, ages),
                               console.log);
    totalYoungAge(users);
    curry
  • def
    不活性評価(遅延評価)を許可する関数.
  • const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);
    const curry2 = f => function curried(...as) {
      return as.length >= f.length ? f(...as) : (...as2) => curried(...as, ...as2);
    }
  • usage
  • // 1. 기존함수의 커리화
    const filter = curry((f, iter) => {
      const res = [];
      for (const el of iter) if (f(el)) res.push(el);
      
      return res;
    });
    
    const map = curry((f, iter) => {
      const res = [];
      for (const el of iter) res.push(f(el));
    
      return res;
    });
    
    const reduce = curry((f, acc, iter) => {
      if (!iter) {
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
      }
      for (const el of iter) acc = f(acc, el);
      
      return acc;
    });
    
    // 2. 가독성 증가
    go(users,
       filter(u => u.age < 42),
       map(u => u.age),
       reduce((a, b) => a + b),
       console.log);
    range
  • def
  • const range = (length) => {
      const res = [];
      for (let i = 0; i < length; i++) res.push(i);
    
      return res;
    }
  • usage
  • range(5); 
    L.range
  • def
  • L.range = function *(length) {
      for (let i = 0; i < length; i++) yield i;
    }
  • usage
  • L.range(5); 
    take
  • def
  • const take = curry((length, iter) => {
      const res = [];
      iter = iter[Symbol.iterator]();
      
      for (const a of iter) { 
        res.push(a);
        if (res.length === length) return res;
      }
      return res;
    });
  • usage
  • take(5, range(100));
    take(5, L.range(Infinity));
    go(
      L.range(Infinity),
      take(5),
      reduce((a, b) => a + b)
    );
    L.map
  • def
  • L.map = curry(function* (f, iter) {
      for (const a of iter) {
        yield f(a);
      }
    });
  • usage
  • let it = L.map(a => a * a, [1, 2, 3]);
    [...it]; // [1, 4, 9]
    L.filter
  • def
  • L.filter = curry(function* (f, iter) {
      for (const a of iter) {
        if (f(a)) yield a;
      }
    });
  • usage
  • let it = L.filter(a => a % 2, [1, 2, 3, 4]);
    [...it]; // [1, 3]
    join
  • def
  • const join = curry((sep = ",", iter) => reduce((a, b) => `${a}${sep}${b}`, iter));
  • usage
  • join(" ", ["Hello", "world", "!"]);
    find
  • def
  • const find = curry((f, iter) => go(
      iter,
      L.filter(f),
      take(1),
      ([a]) => a
    ));
  • usage
  • find(u => u.age < 42, users);
    map with L.map
    const map = curry(pipe(L.map, take(Infinity)));
    filter with L.filter
    const filter = curry(pipe(L.filter, take(Infinity)));
    L.flatten
  • def
  • //  ver.1
    L.flatten = function *(iter) => {
      for (const a of iter) {
        if (a && a[Symbol.iterator]) for (const b of a) yield b;
        else yield a;
      }
    }
    //  ver.2
    L.flatten = function *(iter) => {
      for (const a of iter) {
        if (a && a[Symbol.iterator]) yield *a;
        else yield a;
      }
    }
  • usage
  • L.flatten([[1, 2], 3, 4, [5, 6]]);
    flatten
  • def
  • const flatten = pipe(L.flatten, take(Infinity));
  • usage
  • flatten([[1, 2], 3, 4, [5, 6]]);
    L.deepFlat
  • def
  • L.deepFlat = function *f(iter) {
      for (const a of iter) {
        if (a && a[Symbol.iterator]) yield *f(a);
        else yield a;
      }
    }
    L.flatMap
  • def
  • L.flatMap = curry(pipe(L.map, L.flatten));
    flatMap
  • def
  • const flatMap = curry(pipe(L.map, flatten));
  • usage
  • flatMap(map(a => a * a), [[1, 2], [3, 4]]);
    // [[1, 4], [9, 16]]
    // [1, 4, 9, 16]
    flatMap(L.range, [1, 2, 3])
    // [[0], [0, 1], [0, 1, 2]]
    // [0, 0, 1, 0, 1, 2]
    Promise with functional JS
    promise with reduce, go, pipe
    go,pipe関数はreduceに基づいており,reduceを変更するだけでよい.
  • def
  • // go1: 1번째 인자를 보고, promise 이면 풀어서 전달
    const go1 = (a, f) => a instanceof Promise ? a.then(f) : f(a);
    const reduce = curry((f, acc, iter) => {
    	if (!iter) {
    		iter = acc[Symbol.iterator]();
    		acc = iter.next().value;
    	} else {
    		iter = iter[Symbol.iterator]();
    	}
    	return go1(acc, function recur(acc) {
    		for (const a of iter) {
    			acc = f(acc, a);
    			if (acc instanceof Promise) return acc.then(recur);
    		}
    		return acc;
    	});
    });
  • usage
  • go(
    	Promise.resolve(1),
    	(a) => a + 10,
    	(a) => Promise.resolve(a + 100),
    	(a) => a + 1000,
    	log,
    );
    
    go(
    	Promise.resolve(1),
    	(a) => a + 10,
    	(a) => Promise.reject("Error!"),
    	(a) => a + 1000,
    	log,
    ).catch(a => log(a));
    promise with L.map, map, take
  • def
  • L.map = curry(function *(f, iter) {
      for (const a of iter) {
        yield go1(a, f);
      }
    });
    
      // for of 문은 중간에 끊기는 경우 iterator의 return 메소드를 강제로 실행하기 때문에 여기서는 반드시 while문을 사용해야한다.
      // https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Generator/return
    const take = curry((l, iter) => {
    	const res = [];
    	iter = iter[Symbol.iterator]();
    
    	return (function recur() {
    		let cur;
    		while (!(cur = iter.next()).done) {
    			const a = cur.value;
    			if (a instanceof Promise) {
    				return a.then((a) => ((res.push(a), res.length === l) ? res : recur()));
    			}
    			res.push(a);
    			if (res.length === l) return res;
    		}
    		return res;
    	})();
    });
  • usage
  • go([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)],
       L.map(a => a + 10),
       take(2),
       log);
    promise with L.filter, filter, nop, take
  • def
  • // Symbol nop 을 이용해 진짜 오류인지 구별
    const nop = Symbol("nop");
    L.filter = curry(function* (f, iter) {
    	for (const a of iter) {
    		const b = go1(a, f);
    		if (b instanceof Promise)
    			yield b.then((b) => (b ? a : Promise.reject(nop)));
    		else if (b) yield a;
    	}
    });
    
    // take 에서 nop 체크로 흘려주기
    const take = curry((l, iter) => {
    	const res = [];
    	iter = iter[Symbol.iterator]();
    
    	return (function recur() {
    		let cur;
    		while (!(cur = iter.next()).done) {
    			const a = cur.value;
    			if (a instanceof Promise) {
    				return a
    					.then((a) => ((res.push(a), res.length === l) ? res : recur()))
    					.catch((e) => (e === nop ? recur() : Promise.reject()));
    			}
    			res.push(a);
    			if (res.length === l) return res;
    		}
    		return res;
    	})();
    });
  • usage
  • go(
    	[1, 2, 3, 4, 5, 6],
    	L.map((a) => Promise.resolve(a * a)),
    	L.filter((a) => a % 2),
    	take(3),
    	log,
    );
    reduce with nop
  • def
  • const reduceF = (acc, a, f) => {
      return a instanceof Promise ?
        a.then(a => f(acc, a), e => e == nop ? acc : Promise.reject(e))
      : f(acc, a);
    }
    
    const head = iter => go1(take(1, iter), ([h]) => h);
          
    const reduce = curry((f, acc, iter) => {
      if (!iter) return reduce(f, head(iter = acc[Symbol.iterator]()), iter);
      iter = iter[Symbol.iterator]();
      
      return go1(acc, function recur(acc) {
    	let cur;
    	while (!(cur = iter.next()).done) {
    		acc = reduceF(acc, cur.value, f);
    		if (acc instanceof Promise) return acc.then(recur);
    	}
    	return acc;
      });
    });
    2.usage
    go(
      [1, 2, 3, 4,],
      L.map(a => Promise.resolve(a * a)),
      L.filter(a => Promise.resolve(a % 2)),
      reduce(add),
      log);
    並列評価
    C.reduce, C.take
    1.def
    const C = {} // Concurrency
    
    function noop() {}
    
    const catchNoop = (arr) => (
    	arr.forEach((a) => (a instanceof Promise ? a.catch(noop) : a)), arr
    );
    
    // 전개연산자를 쓴 시점에서 next()가 실행되고 promise들이 실행 됨.
    C.reduce = curry((f, acc, iter) => iter ?
      reduce(f, catchNoop(...iter)) :
      reduce(f, catchNoop(...acc)));
    
    C.take = curry((l, iter) => take(l, catchNoop([...iter])))
    C.takeAll = C.take(Infinity)
    2.usage
    const delay = (a, time) =>
    	new Promise((resolve) => {
    		setTimeout(() => resolve(a), time);
    	});
    
    go(
    	[1, 2, 3, 4, 5],
    	L.map((a) => delay(a * a, (6 - a) * 1000)),
    	L.filter((a) => a % 2),
        C.take(2), // 병렬적으로 모두 실행하지만 2개의 결과만 추출
    	C.reduce(add),
    	log,
    );
    C.map, C.filter
  • def
  • C.map = curry(pipe(L.map, C.takeAll));
    C.filter = curry(pipe(L.filter, C.takeAll));
  • usage
  • C.map(a => delay1000(a * a), [1, 2, 3, 4]).then(log)
    C.filter(a => delay1000(a % 2, a), [1, 2, 3, 4]).then(log)