2. 함수형으로 전환하기

var user = [
{ id: 1, name: 'ID', age: 36 },
{ id: 2, name: 'BJ', age: 32 },
{ id: 3, name: 'JM', age: 32 },
{ id: 4, name: 'PJ', age: 27 },
{ id: 5, name: 'HA', age: 25 },
{ id: 6, name: 'JE', age: 26 },
{ id: 7, name: 'JI', age: 31 },
{ id: 8, name: 'MP', age: 23 },
]

명령형 프로그래밍 보다 선언적 프로그래밍#

  • 코드가 간결
  • 오류와 실수 감소
  • 정확하게 코딩했다는 확신을 느끼기 쉽다.

명령형 코드#

  1. 30 세 이상인 users 를 거른다.
const temp_users = [];
for (let i = 0; i < users.length; i++) {
if (users[i].age >= 30) {
temp_users.push(users[i]);
}
}
console.log(temp_users);
  1. 30세 이상인 users의 names를 수집한다.
const names = [];
for (let i = 0; i < temp_users.length; i++) {
names.push(temp_users[i].name);
}
console.log(names);
  1. 30세 미만인 users를 거른다.
const temp_users = [];
for (let i = 0; i < users.length; i++) {
if (users[i].age < 30) {
temp_users.push(users[i]);
}
}
console.log(temp_users);
  1. 30세 미만인 users의 ages를 수집한다.
const ages = [];
for (let i = 0; i < temp_users.length; i++) {
ages.push(temp_users[i].age);
}
console.log(ages);

_filter, _map 으로 리팩토링#

명령형 코드 1번과 3번의 중복을 제거해 보자.

function _filter(users, predi) {
const new_list = [];
for (let i = 0; i < users.length; i++) {
// 어떤 조건일 때 filter 를 할 것인가를 predi 라는 함수에 완전히 위임한다.
if (predi(users[i])) {
new_list.push(users[i]);
}
}
return new_list;
}
console.log(
_filter(users, function(user) { return user.age >= 30 }),
_filter(users, function(user) { return user.age < 30; }),
);

users 말고도 규격을 맞춘다면 다른 객체를 사용할 수 도 있다.

console.log(
_filter([1, 2, 3, 4], function(num) { return num % 2; }),
)

이제 users 를 list 로 일반화 시켜보자.

function _filter(list, predi) {
const new_list = [];
for (let i = 0; i < list.length; i++) {
// 어떤 조건일 때 filter 를 할 것인가를 predi 라는 함수에 완전히 위임한다.
if (predi(list[i])) {
new_list.push(list[i]);
}
}
return new_list;
}

명령형 코드 2 와 4 의 중복을 제거해 보자.

function _map(list, mapper) {
const new_list = [];
for (let i = 0; i < list.length; i++) {
new_list.push(mapper(list[i]));
}
return new_list;
}
const over_30 = _filter(users, function(user) { return user.age >= 30 });
const names = _map(over_30, function(user) {
return user.name;
});
const under_30 = _filter(users, function(user) { return user.age < 30 });
const ages = _map(over_30, function(user) {
return user.age;
});
_map([1, 2, 3], function(num) { return num * 2 });
console.log(
_map(
_filter(users, function(user) { return user.age >= 30; }),
function(user) { return user.name; }));
console.log(
_map(
_filter(users, function(user) { return user.age < 30; }),
function(user) { return user.age; }));

each#

filter 와 map 의 중복을 제거하자

  1. for loop
  2. list[i]
function _each(list, iter) {
for (let i = 0; i < list.length; i++) {
iter(list[i]);
}
return list;
}
function _filter(list, predi) {
const new_list = [];
_each(list, function(val) {
if (predi(val)) new_list.push(val);
});
return new_list;
}
function _map(list, mapper) {
const new_list = [];
_each(list, function(val) {
new_list.push(mapper(val));
});
return new_list;
}

다형성#

  • map, each, filter 는 함수가 아닌 자바스크립트 array.prototype 의 메서드 이다.

    • 이것은 순수함수가 아니며, 객체의 상태에 따라 결과가 달라진다.
    • 메서드는 객체지향 프로그래밍이다.
    • 메서드는 해당 클래스의 인스턴스에서만 사용할 수 있다.
    • array 가 아니면 사용할 수 없다
    • 다형성을 지원하기가 어렵다.
  • array like 객체

    • javascript 에 array 가 아닌데 array 같이 여겨지는 객체들
    • jquery 객체
    • document.querySelectorAll('body');
    • document.querySelectorAll('*');
      • 배열이 아닌 NodeList.
  • 함수형 프로그래밍의 높은 다형성

    • 그리고 우리가 만든 map 함수를 사용한다면 array like 도 사용할 수 있다.
    • length / key:value 형태만 만족하면 된다.

외부 다형성#

array_like, arguments, document.querySelectorAll 은 map 이나 filter 가 담당

내부 다형성#

우리가 만든 predi, iter, mapper 은 배열안에 어떤 값이든 들어있어도 무언가를 수행할 수 있게 만드는 역할을 하는 보조 함수

_map([1, 2, 3, 4], function(v) {
return v + 10;
})

함수의 두번째 인자를 보통 callback 함수라고 부르는데, 사실 역할에 따라서 부르는 이름이 달라진다.

  • callback 함수: 어떤 일을 다 끝난 후에 리턴하는 함수
  • predicate : 조건을 리턴하는 함수
  • iterator(?): 돌면서 반복적으로 실행되는 함수
  • mapper: 무언가의 사이를 매핑하는 함수

보조 함수의 이름을 다르게 불러주자

커링 curry, curryr#

함수와 인자를 다루는 기법. 함수의 인자를 하나씩 적용해 나가다가, 필요한 인자가 모두 채워지면 함수본체를 실행한다.

JavaScript 는 커링을 지원하지 않지만, 일급함수가 지원되고 평가시점을 마음대로 다룰 수 있기 때문에 커링을 구현할 수 있다.

curry#

function _curry(fn) {
return function(a) {
return function(b) {
return fn(a, b);
}
}
}

curry 가 아닌 예

const add = function(a, b) {
return a + b;
};
console.log(add(10, 5)); // 15

curry 를 사용한 예

const add2 = _curry(function(a, b) {
return a + b;
});
const add10 = add2(10);
console.log(add10(5)); // 15
console.log(add2(5)(3)); // 15

curry 의 변형

function _curry(fn) {
return function(a, b) {
if (arguments.length === 2) return fn(a, b);
return function(b) {
return fn(a, b);
}
}
}
console.log(add2(1, 2))
function _curry(fn) {
return function(a, b) {
return arguments.length === 2 ? fn(a, b) : function(b) { return fn(a, b); };
}
}
console.log(add2(1, 2))

curry 를 이용한 빼기 함수

const sub = _curry(function(a, b) {
return a - b;
})
console.log(sub(10, 5)); // 5

_curryr#

인자를 오른쪽에서 부터 채워나감

const sub10 = sub(10);
console.log(sub10(5)); // 5

인자를 를 반대로 채워넣고 싶을때

function _curryr(fn) {
return function (a, b) {
return arguments.length === 2 ? fn(a, b) : function(b) { return fn(b, a)}
}
}
var sub = _curryr(function(a, b) {
return a - b;
})
console.log(sub(10, 5));
const sub10 = sub(10);
console.log(sub10(5)); // -5

_get#

object 의 값을 안전하게 참조하는 함수

function _get(obj, key) {
return obj === null ? undefined : obj[key];
}

있는 값을 참조할 때

const user1 = users[0];
console.log(user1.name);
console.log(_get(users1, 'name'));

만약 없는 값을 참조하려 할 때

console.log(users1[10].name) // undefined Type Error
console.log(_get(users[10], 'name')); // undefined

커링을 이용한 _get

const _get = _curryr(function(obj, key) {
return obj === null ? undefined : obj[key];
})
console.log(_get('name')(user1));

name 을 꺼내는 함수를 만들어 재활용 할 수 있다.

const get_name = _get('name');
console.log(get_name(user1));
console.log(get_name(user1[3]));

이전의 _map 을 사용한 코드를 더 간결하게 할 수 있다.

_map(
_filter(users, function(user) { return user.age >= 30; }),
function(user) { return user.name; })
);
_map(
_filter(users, function(user) { return user.age < 30; }),
function(user) { return user.age; })
);

after

_map(
_filter(users, function(user) { return user.age >= 30; }),
_get('name')
)
_map(
_filter(users, function(user) { return user.age < 30; }),
_get('age'))
)

_reduce#

리스트의 수만큼 iter 을 반복한다.

function _reduce(list, iter, memo) {
iter(iter(iter(0, 1), 2),3 );
}
console.log(
_reduce([1,2,3], add, 0)
)
memo = add(0,1);
memo = add(memo, 2);
memo = add(memo, 3);
return memo;

reduce 는 원래들어온 자료와 다른, 새로운 축약된 형태의 자료로 가공하기 위해 사용된다.

filter나 map 은 array 로 들어온 것을 array 로 다시 리턴한다.

reduce 는 array 로 들어온 것을 다른 형태로 만들 수 있다.

var slice = Array.prototype.slice;
function _rest(list, num) {
return slice.call(list, num || 1);
}
function _reduce(list, iter, memo) {
// memo 가 없을 경우
if (arguments.length == 2) {
memo = list[0];
// array like 처리
list = _rest(list);
}
_each(list, function(val) {
memo = iter(memo, val);
});
return memo;
}

파이프라인 _pipe, _go, 화살표 함수#

함수들을 인자로 받아서 함수들을 연속적으로 실행해 주는 함수

함수를 연속적으로 실행하는 함수를 리턴한다.

function _pipe() {
var fns = arguments;
return function(arg) {
return _reduce(fns, function(arg, fn) {
return fn(arg)
}, arg);
}
}
var f1 = _pipe(
function(a) { return a + 1; },
function(a) { return a * 2; },
function(a) { return a * 2; },
);
console.log(f1(1));

함수형 프로그래밍#

  • 함수가 함수를 실행한다.
  • 함수가 함수를 리턴한다.
  • 함수의 평가시점, 함수의 인자들이 적용되어가는 과정에서 Side Effect 가 없고 순수함수들로 구성된다.
  • 조합성을 강조한다.
  • 추상화의 단위를 함수로 한다.

고차함수#

함수를 인수로 받는 함수 ex) map, reduce... 함수를 반환하는 함수 ex) bind

다형성 높이기, _keys, 에러#

함수형프로그래밍에서는, 예외적인 데이터가 들어오면,

  • 다형성을 높이는 방법으로 해결할 수 있다.

_each 의 외부 다형성 높이기#

_each(null, console.log) 는 error 가 발생한다. _each 함수 내부에서 list.length length 를 참조하고자 할 때 list 가 null 이기 때문이다.

에러가 나지 않게 하기 위해서는

  • _get 함수는 null 체크를 해주고 있다.

  • _get 함수를 통하여 list.lengthundefined 가 들어간다면 에러는 나지 않고

  • i < undefined 가 false 이기 때문에 for 문을 수행하지 않게 된다.

    // _curryr 이용하여
    var _length = _get('length');
    function _each(list, iter) {
    for (var i = 0, len = _length(list); i < len; i++) {
    iter(list[i]);
    }
    return list;
    }

함수형프로그래밍 에서는 예외적인 데이터가 들어와도 에러가 나지 않도록 유연하게 구현한다. (underscore.js 에서 구현하는 방법)

  • if 로 타입을 체크 한다거나, try-catch 를 쓰지 않는다.
Last updated on