Higher Order Function - 고차함수

From Effective JavaScript - 아이템 19 "고차 함수에 익숙해져라"

한때 함수형 프로그래밍의 특별한 기능처럼 사용되었던 고차(Higher-Order)함수는 ,고급 프로그래밍 기법을 뜻하는 심오한 용어처럼 보인다. 하지만 이는 진실이 아니다. 함수의 간결하고 우아한 기법을 활용하게 되면 코드가 더 간단하고 명료해진다. 몇 년 동안, 스크립트 언어들은 이런 기법들을 받아들여 왔고, 그 과정 중에 함수형 언어의 최고 코딩 관례 중 일부를 채용했다.

고차 함수는 다른 함수를 인자로 받거나 그 결과로 함수를 반환하는 함수다. 인자로 받는 함수(흔히 콜백 함수로 불리는데, 고차 함수로 인해 되불려지기(callback) 때문이다)는 특히 강력하고 표현력 높으며 자바스크립트에서 자주 쓰이는 코딩 관례다.

배열의 표준 정렬 메서드 sort를 생각해 보자. 모든 배열에서 동작할 수 있도록, sort 메서드는 호출자에 의존하여 배열 안의 두 요소를 어떻게 비교할지 결정한다.

							function compareNumbers(x , y) {
								if (x < y) {
									return -1;
								}
								if (x > y) {
									return 1;
								}
								return 0;
							}
							[3, 1, 4, 1, 5, 9].sort(compareNumbers);	// 1, 1, 3, 4, 5, 9
						

표준 라이브러리는 호출자로부터 비교 메서드를 가지는 객체를 전달받기를 요구하지만, 하나의 메서드만 필요하기 때문에 함수를 직접 받는 것이 더 간단하고 간결하다. 사실, 이 예제는 익명함수를 통해 더 간단하게 만들 수 있다.

							[3, 1, 4, 1, 5, 9].sort(function (x, y) {
								if (x < y) {
									return -1;
								}
								if (x > y) {
									return 1;
								}
								return 0;						
							}); // 1, 1, 3, 4, 5, 9
						

고차 함수의 사용법을 익혀 두면 지루한 상용문을 제거하고 코드를 간단하게 만들 수 있다. 배열의 여러 일반적인 연산들은 멋진 고차 함수 추상을 가지는데, 이에 익숙해지면 좋다. 문자열로 된 배열을 변환하는 간단한 동작에 대해 생각해 보자. 일반적인 반복문으로 작성한 코드는 다음과 같다.

							var names = ["Fred", "Wilma", "Pebbles"];
							var upper = [];
							for (var i = 0, n = names.length; i < n ; i++) {
								upper[i] = names[i].toUpperCase();
							}
							upper; // ["FRED", "WILMA", "PEBBLES"]
						

(ES5에서 소개돈) 배열의 간단한 map 메서드를 이용하면, 반복문의 세부 사항을 완전히 제거할 수 있고, 각 요소의 변환을 지역 함수 내에 구현하기만 하면 된다.

							var names = ["Fred", "Wilma", "Pebbles"];
							var upper = names.map(function (name) {
								return name.toUpperCase();
							});

							upper; // ["FRED", "WILMA", "PEBBLES"]
						

고차 함수의 사용에 익숙해지고 나면, 직접 작성할 기회도 생길 것이다. 비슷하거나 중복된 코드를 자주 보게 된다면 이는 숨길 수 없는 고차 함수 추상의 신호다. 예를 들어, 알파벳 문자로 문자열을 만드는 프로그램의 일부를 찾았다고 가정해 보자.

							var aIndex = 'a'.charCodeAt(0);
							alert(aIndex);
							var alphabet = "";
							for (var i = 0; i < 26; i++) {
								alphabet += String.fromCharCode(aIndex + i);
							}
							alphabet;	// "abcdefghijklmnopqrstuvwxyz" 
						

반면, 프로그램의 다른 부분에서는 다음과 같이 숫자 값을 포함하는 문자열을 생성한다고 가정하자.

							var digits = "";
							for (var i = 0; i < 10; i++) {
								digits += i;
							}
							digits;	// "0123456789"
						

또 다른 부분에서는 , 임의 글자로 문자열을 만든다.

							var random = "";
							for (var i = 0; i < 8; i++) {
								random += String.fromCharCode(Math.floor(Math.random()*26) + aIndex); 
							}
							random;		// "bdwvfrtp" (매번 다른 결과를 반환함)
						

각 예제는 서로 다른 문자열을 생성하지만, 모두 공통의 로직을 공유한다. 모든 반복문은 각각의 개별적인 부분을 생성하기 위해 어떤 계산을 하고, 그 결과를 합쳐 문자열을 생성한다. 공통 부분을 추출하고 하나의 유틸리티 함수로 옮기면 다음과 같은 코드를 만들 수 있다.

							function buildString(n, callback) {
								var result = "";
								for (var i = 0; i < n; i++) {
									result += callback(i);
								}
								return result;
							}
						

buildString 구현이 반복문들의 공통 부분을 어떻게 포함시켰는지를 주목하여 살펴보자. 공통 부분은 다양한 파라미터를 사용한다. 반복문을 순회하는 회수는 변수 n이 되고, 문자열을 생성하는 부분은 callback 함수를 호출하게 되었다. 이제 buildString을 이용하면 이전의 세 예제를 다음과 같이 간단하게 구현할 수 있다.

							var alphabet = buildString(26, function (i) {
								return String.fromCharCode(aIndex +i);
							}) 
							alphabet;	// "abcdefghijklmnopqrstuvwxyz" 

							var digits = buildString(10, function (i) {
								return i;
							})
							digits;	// "0123456789"

							var random = buildString(8, function () {
								return String.fromCharCode(Math.floor(Math.random()*26) + aIndex);
							})
							random;		// "ltvisfjr" (매번 다른 결과를 반환함)
						

고차 함수 추상을 생성하는 방식에는 장점이 많다. 구현시 반복문 경계 부분의 상태를 올바르게 지정하기가 어려운 부분이 있다면, 고차 함수로 구현하면 지역화된다. 고차 함수를 사용하면, 로직 내의 어떤 버그를 수정할 때 프로그램 전체에 퍼져 있는 코딩 패턴의 모든 사례를 고치는 대신, 단 한번만 수정하면 된다. 또한 연산의 효율성을 최적화할 필요가 있다고 판단될 때도 역시 한군데만 수정하면 모든 처리가 가능하다. 마지막으로 추상에 buildString 같은 명백한 이름을 지정해주면 코드를 읽는 사람이 구현의 세부 사항을 해석할 필요 없이 코드가 어떤 동작을 하는지 쉽게 이해할 수 있다.

고차 함수의 사용법을 익히고 나면 동일한 패턴을 반복적으로 작성하여 더 간결한 코드를 만들게 되고, 더 높은 생산성을 얻게 되며, 가독성 또한 개선하게 된다. 항상 공통 패턴을 눈여겨 보고 고차 유틸리티 함수로 옮기는 개발 습관이 매우 중요하다.

기억할 점

  • - 고차 함수는 다른 함수를 인자로 받거나 그 결과로 함수를 반환하는 함수다.
  • - 이미 존재하는 라이브러리에 포함된 고차 함수의 사용에 익숙해져라.
  • - 고차 함수로 대체할 수 있는 공통 코딩 패턴을 찾는 방법을 익혀라.