map
- 직접 변화를 일으키는 메소드, 함수에 보내는 것이 아닌 결과를 return 하는 값을 활용하도록 함.
- 함수형 프로그래밍에서 map 함수는 iterable 내 일대일로 매핑되는 어떠한 값을 수집하겠다는 보조 함수를 전달하는 방식으로 사용.
- 함수를 값으로 다루면서 원하는 시점에서 인자를 적용하는 고차함수이기도 함.
const products = [ {name : '반팔티', price:15000}, {name : '바지', price:25000} ]; //기존 코드 let names =[]; for(const p of products) { names.push(p.name) } console.log(names) //[ '반팔티', '바지' ] //map 로직 const map = (f, iter) => { let res = []; for(const a of iter) { res.push(f(a)); } return res; } console.log(map(p => p.name, products)); //[ '반팔티', '바지' ]
map의 다형성
console.log(document.querySelectorAll('*'.map(el => el.nodeName))) //map을 찾을 수 없음
- querySelector는 array로 표시되지만 array를 상속받지 않음 → 프로토타입에 map함수가 구현되어있지 않음.
- 하지만 다음과 같은 경우 잘 동작함.
console.log(map(el => el.nodeName, document.querySelectorAll('*'))) //정상적으로 출력됨.
- document.querySelector가 이터러블 프로토콜을 따르기 때문.
const it = document.querySelectorAll('*')[Symbol.iterator()]();
로 이터레이터가 나오고, next를 통해 내부 값을 순회하는 결과를 볼 수 있음.
- map은 array를 포함해 이터러블 프로토콜을 따르는 많은 함수를 사용할 수 있음 → 대부분 map으로 컨트롤 할 수 있음, 다른 헬퍼 함수들과 조합성이 좋음.
//다른 사례 (map function 위와 동일하게 적용된 상태로 진행) //제너레이터 function *gen() { yield 2; if (false) yield 3; yield 4; } console.log(map(a => a*a, gen())); //[ 4, 16 ] //JS에서 key value 쌍을 표현하는 Map 또한 이터러블이다. let m = new Map(); m.set('a', 10); m.set('b', 20); console.log(new Map(map( ([k, a]) => [k, a*2], //구조분해를 통해 key와 value를 나눠 가짐. m)) ); //Map { 'a' => 20, 'b' => 40 }, set을 이용해 만든 map을 또 다른 map객체로 만듦.
filter
const products = [ {name : '반팔티', price:15000}, {name : '바지', price:25000} ]; //기존 코드 let under20000 = []; for(const p of products) { if(p.price < 20000) under20000.push(p); } console.log(under20000); //[ { name: '반팔티', price: 15000 } ] //filter 로직 const filter = (f, iter) => { let res = []; for(const a of iter) { if(f(a)) res.push(a); } return res; } console.log(...filter(p=>p.price <20000, products)); //{ name: '반팔티', price: 15000 } console.log(filter(p=>p.price <20000, products)); //[ { name: '반팔티', price: 15000 } ] console.log(filter(n => n%2, [1,2,3,4])); //[ 1, 3 ]
- n ⇒ n%2 : 보조함수, 내부에 있는 다형성은 보조함수를 통해 지원.
- [1,2,3,4] : 외부는 이터러블 프로토콜을 따르기 때문에 다형성을 지원함.
- 결국 filter로 다양하게 걸러낼 수 있음.
reduce
iterable을 하나의 값, 다른 값으로 축약하는 함수.
const nums = [1,2,3,4,5]; //기존 코드 let total = 0; for(const n of nums) { total = total + n; } console.log(total) //reudce 로직 const reduce = (f, acc, iter) => { if(!iter) { iter = acc[Symbol.iterator](); acc = iter.next().value; //next로 맨 앞 숫자를 acc로 전달함. } for(const a of iter) { acc = f(acc,a); } return acc; } const add = (a,b) => a+b; console.log(reduce(add, 0, nums)); //15 console.log(reduce(add,[1,2,3,4,5])); //15
map+filter+reduce 중첩 사용과 함수형 사고
const products = [ {name : '반팔티', price:15000}, {name : '긴팔티', price:20000}, {name : '케이스', price:15000}, {name : '후드티', price:30000}, {name : '바지', price:25000} ]; const add = (a,b) => a+b; console.log( reduce(add, map(p => p.price, filter(p => p.price < 20000, products)))); //30000 console.log( reduce(add, filter(n => n < 20000, map(p => p.price, products)))); //30000
map(p => p.price, products)
가 숫자로 구성된 배열이 있을거란 기대로 작동하는 것처럼 연속적인 함수가 있어도 하나씩 생각하면 됨.
- 마찬가지로 products의 자리도 map을 사용할 때, 특정 조건을 충족한 배열이 평가되도록 코드를 작성하면 되는 것임.
코드를 값으로 다루어 표현력 높이기
함수형 프로그래밍에서는 코드를 값으로 아이디어를 많이 사용하기 때문에 코드의 표현력을 높혀야 함.
go
const go = (...args) => reduce((a, f) => f(a), args); go( 0, a => a + 1, a => a + 10, a => a + 100, console.log //결과값으로 로그 전달 ); //111
pipe
함수를 return하는 함수. 함수들이 나열된 합성된 함수를 만드는 함수임.
const go = (...args) => reduce((a, f) => f(a), args); const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs); // f랑 ...fs를 매개변수로 받아서 함수A를 리턴한다 // 함수A는 ...as를 매개변수로 받아서 go(f(...as), ..fs)의 결과를 리턴한다. go( 0, a => a + 1, a => a + 10, a => a + 100, console.log ) const pipeRet = pipe( (a,b) => a+b, // 첫번째 인자 f a => a + 10, // 나머지 인자들 ...fs a => a+100, console.log ); //as = 0, 1 const ret = pipeRet(0, 1); //go(f(0,1), a=>a+10, a=>a+100)의 결과를 리턴한다. //위에서 말하는 f는 (a,b) => a+b //f(0, 1) ==> 1 // a=>a+10 ==> 11 // a=>a+100 ==> 111
- pipe함수는 f, as, fs 등 받아야 할 값이 많음. 따라서 매개변수를 받는 타이밍을 결정한다는 점에서 좋음.
go를 사용하여 읽기 좋은 코드로 만들기
위에서부터 아래로 읽기 쉽게 표현.
const products = [ {name : '반팔티', price:15000}, {name : '긴팔티', price:20000}, {name : '케이스', price:15000}, {name : '후드티', price:30000}, {name : '바지', price:25000} ]; const go = (...args) => reduce((a, f) => f(a), args); const add = (a,b) => a+b; go( products, products => filter (p => p.price < 20000, products), products => map(p => p.price, products), prices => reduce(add, prices), console.log );
go+curry를 사용하여 더 읽기 좋은 코드로 만들기
순서를 바꾸는 go 함수와 함수를 부분적으로 실행하는 curry 함수를 이용해 표현 가능.
const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a,..._); const mult = curry((a,b) => a*b); console.log(mult(3)(4)); //12 const mult3 = mult(3); console.log(mult3(10)); //30 console.log(mult3(5)); //15 console.log(mult3(3)); //9 //go 함수 축약하기, 기존 메소드를 curry로 감싸야 함! go( products, filter(p => p.price < 20000), map(p => p.price), reduce(add), console.log ) //30000
go 변화 비교
console.log( reduce(add, map(p => p.price, filter(p => p.price < 20000, products)))); //30000 go( products, products => filter (p => p.price < 20000, products), products => map(p => p.price, products), prices => reduce(add, prices), console.log ); go( products, filter(p => p.price < 20000), map(p => p.price), reduce(add), console.log )
함수 조합으로 함수 만들기
고차함수를 함수의 조합으로 만들며 잘게 나눈 함수로 중복을 제거하고 사용성을 확장시킴.
const total_price = pipe( map(p => p.price), reduce(add) ); const base_total_price = predi => pipe ( filter(predi), total_price ); go( products, base_total_price(p => p.price < 20000), console.log ) go( products, base_total_price(p => p.price >= 20000), console.log )