이 포스트는 인프런에서 진행한 유인동 님의 함수형 자바스크립트를 듣고 감명 받아서 쓴 글이다. 사실 underscore, lodash 등 함수형 패러타임으로 코드를 짤 수 있게 끔 미리 이런 함수들을 제공하는 라이브러리들을 쓰고, 이 포스트는 그닥 볼 필요가 없다. 하지만 이런 원리를 알고 접근을 하다보면 위 라이브러리를 쓴다고 하더라도 추가로 필요한 나의 코드를 함수형으로 더 짜기 유용하지 않을까?
find
find는 두 말 하면 입 아프겠지만, 기존 데이터 사이에서 원하는 것을 찾을 때 사용한다. 아래 예제를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
const nums = [1, 3, 5, 7, 100]; let no = 0; for(const num of nums) { if(!(num % 2)) { // 짝수인지 no = num; break; } } const users = [ {name: 'asdf', age: 12}, {name: 'qwer', age: 33} ]; let name = ''; let over30 = null; for(const user of users) if(user.age > 30) return over30 = user; console.log(no); // 100 console.log(over30.age ); // 33
위 코드를 보면 일단 반복문이 반복해서 쓰이고 있고, 뭘 구할지도 반복되고 있다. 이 부분들을 추상화해보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
const_find = (list, predicate) => { for(const item of list) if(predicate(item)) return item; // 새로운 변수를 만들어서 거기다 값을 담고 리턴하려니 추상화를 해도 오히려 코드가 길어져서 복잡해보인다. //let match; //_each(list, item => { // if(predicate(item)) match = item; //}); //return match }; const nums = [1, 3, 5, 7, 100]; const users = [ {name: 'asdf', age: 12}, {name: 'qwer', age: 33} ]; console.log(_find(nums, num => !(num % 2))); // 100 console.log(_find(users, user => user.age >= 30).age); // 33
findIndex
이렇게 배열(과 ArrayLike)을 넘겨서 그 요소 중에서 내가 원하는 조건을 추상화시킨 _find를 써서 원하는 값을 얻어낼 수 있다. 그럼 이 요소들이 몇 번째에 있는지 구하는 _findIndex도 만들어보자. 기본적으로 순서가 없는, 인덱스로 접근이 불가능한 객체는 무시하고 만들도록 하겠다.
const_some = (list, predicate) => _findIndex(list, item =>predicate(item)) !== -1; // 비트단위의 논리 연산자(Not)인 ~(Tilde)를 쓰면 다음과 같이 할 수 있다. // const _some = (list, predicate) => !!~_findIndex(list, item => predicate(item)); let nums = [1, 3, 100, 2, 7]; console.log(_some(nums, num => !(num % 3))); // true nums = [0, 3, 100, 2, 7]; console.log(_some(nums, num => !(num % 3))); // true
_find는 조건을 만족하는 값이면 어떤 값이든 땡겨올 수 있고 falsy value는 물론, 심지어 undefined도 땡겨올 수 있으므로 boolean으로 캐스팅해도 올바른 결과를 얻어낼 수 없다. _findIndex는 -1만 아니면 참인 것이기 때문에 -1에 대해서만 대비하면 되므로 추상화하기에 적당하다.
every
every는 하나라도 조건을 만족하지 않으면 false를 반환하는 함수다. &&(and)의 특성을 지닌다고 보면 될 것 같다. 바로 어떤 녀석이 every인지 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
let nums = [1, 3, 100, 2, 7]; let no3 = true; for(const num of nums) if(!(num % 3 === 0)) { // 3의 배수가 아니라면 no3 = false; break; } console.log(no3); // false nums = [4, 8, 16, 20]; no3 = true; for(const num of nums) if(!(num % 2 === 0)) { // 2의 배수가 아니라면 no3 = false; break; } console.log(no3); // true
위 코드는 모두 3의 배수인지, 모두 2의 배수인지를 구한 반복문이다. 그럼 한 번 추상화해보자.
1 2 3 4 5 6 7 8
const_every = (list, predicate) => { for(const item of list) if(!(predicate(item))) returnfalse; returntrue; }; let nums = [1, 3, 100, 2, 7]; console.log(_every(nums, num => num % 3 === 0)); // false nums = [4, 8, 16, 20]; console.log(_every(nums, num => num % 2 === 0)); // true
위에 반복문 부분도 _find로 추상화해보고 싶지만 위의 _some과 같이 falsy value 이슈가 있을 것 같다. 그럼 falsy value로 부터 자유로운 _findIndex를 가지고 추상화해보자.
1 2 3 4 5 6 7 8 9 10 11 12
const_findIndex = (list, predicate) => { for(let i=0, len=list.length; i<len; i++) if(predicate(list[i])) return i; return -1; }; const_every = (list, predicate) => _findIndex(list, item => !predicate(item)) === -1; // 비트단위의 논리 연산자(Not)인 ~(Tilde)를 쓰면 다음과 같이 할 수 있다. //const _every = (list, predicate) => !~_findIndex(list, item => !predicate(item));
let nums = [1, 3, 100, 2, 7]; console.log(_every(nums, num => num % 3 === 0)); // false nums = [4, 8, 16, 20]; console.log(_every(nums, num => num % 2 === 0)); // true
오늘은 여기까지만 알아보도록 하자. 다음 시간에는 함수형의 꽃이라 할 수 있는 curry에 대해 알아보도록 하자.