JavaScript: 함수형 프로그래밍

chaewonkang·2020년 9월 25일
0

JavaScript

목록 보기
1/2

리액트를 공부하기 위해서 함수형 프로그래밍은 필수로 알아야 하는 개념이다. 특히 리덕스, 플럭스 등의 상태 관리 프레임워크를 이용하기 위해서도 함수형 자바스크립트 패러다임을 알아야 한다. 언젠가 해야 할 거라면 지금 해야지. 함수형 프로그래밍이 도대체 뭐길래... 하는 마음으로 연재를 시작해 본다.

함수형 프로그래밍의 기본 약속은, 함수가 1급 시민(first class citizen) 혹은 1급 멤버(first class member) 여야 한다는 데에 있다. 1급 시민이라는 것은, 변수에 함수를 대입할 수 있고, 함수를 다른 함수에 인자로 넘길 수도 있고, 함수에서 함수를 만들어서 함수를 리턴할 수도 있어야 한다는 뜻이다. 즉, 함수를 정수나 문자열 같은 다른 데이터 타입과 마찬가지로 취급할 수 있다는 뜻이다.

이 내용이 잘 이해 되지 않는다면, 잘 정리된 다음 링크를 참조하면 더욱 도움이 될 것이다. 특히 ES6 명세에서는, 함수형 프로그래밍 기법을 더욱 잘 활용할 수 있는 화살표 함수, 프라미스, 스프레드 연산자 등이 추가되었다. 그러니 함수형 프로그래밍을 공부하고자 한다면, ES6 명세를 확실하게 배운 뒤 진입하는 것을 추천한다.

함수형 프로그래밍을 알기 위해서는, 추가로 '고차 함수'와 '선언적 프로그래밍', '순수 함수'에 대해 알아야 한다.

고차 함수

함수를 인자로 받거나, 함수를 반환하는 함수를 '고차 함수'라고 한다. ES6명세의 화살표 함수 문법을 사용한 고차 함수 예제를 보자.

const createScrem = logger => message =>
	logger(message.toUpperCase() + "!!!")

이처럼 화살표가 두 개 이상이면 고차 함수이니 유의해서 본다.

선언적 프로그래밍

명령적 프로그래밍과 반대되는 개념.

명령형 프로그래밍은 무엇을 어떻게 할 것인가에 가깝고, 선언형 프로그래밍은 무엇을 할 것인가에 가깝다. 따라서 선언형 프로그래밍은 읽기 쉽고, 추론하기 쉽다.

선언적 프로그래밍은, 필요한 것을 달성하는 과정을 하나하나 기술하는 것 보다 필요한 것이 어떤 것인지 기술하는 데에 방점을 두고 프로그래밍의 구조를 세운다. 자세한 설명은 다음 링크를 참조한다.

순수 함수

파라미터에 의해서만 반환값이 결정되는 함수를 말한다. 순수 함수는 최소 하나 이상의 인자를 받고, 인자가 같으면 항상 같은 값이나 함수를 반환한다. 순수 함수에는 부수 효과side effect가 없다. 부수효과라 함은, 전역 변수를 설정하거나, 함수 내부의 다른 상태를 변경하는 것을 말한다.

리액트에서는 UI를 순수 함수로 표현한다. 이에 관련해서는, 똑똑한 컴포넌트와 멍청한 컴포넌트로 성격을 나뉘어 개발 단계에서부터 적용하는 설계 방식을 참고하면 도움이 된다. 여기에 아주 잘 정리된 내용이 있으니 참고한다.

순수 함수 프로그래밍 규칙

  1. 데이터를 변경 불가능하게 유지한다.
  2. 함수를 순수 함수로 만든다. 인자를 적어도 하나 이상 받게 만들고, 데이터나 다른 함수를 반환해야 한다.
  3. (가능하면) 루프보다는 재귀를 사용한다.

참고할 점! 인자(argument)는 함수를 호출하는 쪽에서 함수에 넘기는 값 (실인자, actual argument라고도 함)을 의미하며, 파라미터(parameter)는 함수를 정의할 때 매개변수를 받기 위해 사용한 변수 이름(형식인자, formal parameter)을 의미한다.

함수형 프로그래밍

1. 불변성

함수형 프로그래밍에서는 데이터의 불변성을 지킨다. 데이터 구조의 복사본을 만들고 그 중 일부를 변경하는 식으로 원본 데이터를 보호할 수 있다.

let color_shirt = {
	title: "Margiela",
	color: "Blue",
	rating: 0
}

function rateColor(color, rating) {
	color.rating = rating;
	return color;
}

console.log(rate(color_shirt, 5).rating); // result: 5
console.log(color_shirt.rating); // result: 5

자바스크립트에서 함수의 인자는 실제 데이터에 대한 '참조'다. rateColor 함수 안에서 color 객체의 rating 필드의 값을 변경하면, 원본 color_shirt 객체의 rating 필드의 값도 바뀐다. 이를 방지하기 위해 아래와 같이 코드를 쓴다.

var rateColor = function(color, rating) {
	return Object.assign({}, color, {rating:rating});
}

console.log(rate(color_shirt, 5).rating); // result: 5
console.log(color_shirt.rating); // result: 0

Object.assign() 을 사용해서 색의 평점을 바꾸는데, 이는 복사기와 같다. Object.assign은 빈 객체를 받고, color 객체를 그 빈 객체에 복사하고, 복사본에 있는 rating 프로퍼티의 값을 rating 파라미터의 값으로 변경한다.

ES6의 화살표 함수와 ES7의 객체 스프레드 연산자를 활용해 같은 함수를 작성할 수 있다.

const ratecolor = (color, rating) =>
	({
		...color,
		rating
	})

참고할 점! 화살표 함수의 본문에서 바로 중괄호'{}'를 사용해 객체를 반환할 수 없기 때문에 꼭 괄호'()'가 필요하다.

Array.push를 사용해서, 어떤 객체에 새로운 필드와 값을 추가하는 함수를 작성할 수 있다. 하지만 Array.push는 불변성 함수가 아니다. 어떤 배열에 새로운 원소를 추가하여, 데이터가 변해버린다. 따라서 이를 방지하기 위해서는 Array.concat을 대신 사용한다.

Array.concat

Array.concat은 두 배열을 붙여준다.

Array.filter

원본 배열로부터 새로운 배열을 만들어내는 자바스크립트 배열 내장 함수. '술어 predicate'를 유일한 인자로 받는다 (불린 값 false, true를 반환). 배열에 있는 모든 원소에 predicate를 한 번씩 호출하여, 반환값이 true면 해당 원소를 새 배열에 넣는다. 이는 순수 함수다.

Array.map

술어가 아닌, '변환 함수'를 인자로 받는다. 함수를 배열의 모든 원소에 적용해서 반환 받은 값으로 이루어진 새 배열을 반환한다.

Array.reduce

고차함수 (high order function, HOF)

다른 함수를 조작할 수 있는 함수. 다른 함수를 인자로 받거나 함수를 반환할 수 있고, 때로는 그 두 가지를 모두 수행한다. 다른 함수를 인자로 받는 함수에는, Array.map, Array.filter, Array.reduce 등이 있고 이들은 모두 고차 함수이다.

다른 함수를 반환하는 고차 함수는 자바스크립트에서 비동기적 실행 맥락을 처리할 때 유용하다. 함수를 반환하는 고차 함수를 이용하면 필요할 경우 재활용할 수 있는 함수를 만들 수 있다.

커링 (Currying)

고차 함수 사용법과 관련한 함수형 프로그래밍 기법. 어떤 연산을 수행할 때 필요한 값 중 일부를 저장하고 나중에 나머지 값을 전달 받는 기법이다. 이를 위해 다른 함수를 반환하는 함수를 사용하며, 이를 커링된 함수라 부른다.

const userLogs = userName => message =>
	console.log(`${userName} -> ${message}`);

const log = userLogs("grandpa23");

log("attemped to load 20 fake members")
getFakeMembers(20).then(
	members => log(`successfully loaded ${members.length} members.`),
	error => log("encountered an error loading members");
)

userLogs는 일부 정보(사용자 이름)을 받고, 함수를 반환한다. 나머지 정보 (message)가 사용 가능해지면, userLogs가 반환한 함수를 활용할 수 있다.

재귀 (recursion)

자기 자신을 호출하는 함수를 만드는 기법. 루프를 모두 재귀로 바꿀 수 있고, 일부 루프는 재귀로 표현하는 쪽이 더 쉽다.

합성 (composition)

함수형 프로그램은 로직을 구체적인 작업을 담당하는 여러 작은 순수 함수로 나눈다. 그 과정에서 언젠가는 모든 작은 함수를 한데 합칠 필요가 있다. 각 함수를 서로 연쇄적으로, 또는 병렬로 호출하거나 여러 작은 함수를 조합해서 더 큰 함수로 만드는 과정을 반복해서 전체 애플리케이션을 구축한다. 합성의 목표는, '단순한 함수를 조합해 고차 함수를 만들어내는 것'이다.

체이닝 (Chainning)

Compose()

함수를 더 큰 함수로 조합해 줌. 고차 함수. 함수를 인자로 받아서 값을 하나 반환

const compose = (...fns) =>
	(arg) =>
		fns.reduce(
			(composed, f) => f(composed),
			arg
		)

하나로 합치기

profile
문학적 상상력과 기술적 가능성

0개의 댓글