본문 바로가기

책 리뷰/함수형 자바스크립트

함수형 자바스크립트 [1장]

※ 함수형 자바스크립트 라는 책을 한 장씩 리뷰해보도록 하겠습니다.

 

Chapter 1. 함수형 길들이기

 

" 객체지향은 가동부를 캡슐화하여 코드의 이해를 돕는다.

함수형 프로그래밍은 가동부를 최소화하여 코드의 이해를 돕는다"

 

어플리케이션 아키텍쳐를 구축하는데에 있어 어떤 문제는 객체지향 설계 방식으로 해결이 가능하나, 자바스크립트는 동적인 언어라 덩어리가 커지면 복잡해지고 가독성이 떨어지며 관리가 힘들어지는 문제점이 있습니다. 이러한 문제와 멀어지기 위해 어플리케이션 설계시 중요하게 봐야할 요소는 아래와 같습니다.

 

  • 확장성 : 추가 기능을 지원하기 위해 계속 코드를 리팩토링해야 하는가?
  • 모듈화 용이성 : 파일 하나를 고치면 다른 파일도 영향을 받는가?
  • 재사용성 : 중복이 많은가?
  • 테스트성 : 함수를 단위 테스트하기 어려운가?
  • 헤아리기 쉬움 : 체계도 없고 따라가기 어려운 코드인가?

위의 요소들에 가까워지게 해줄 수 있는 프로그래밍 패러다임이 함수형 프로그래밍(FP)입니다.

 

1.1 함수형 프로그래밍은 과연 유용한가?

 

자바스크립트 맥락에서 보면, FP 사고 방식은 자바스크립트만의 매우 표현적인 특성을 가다듬어, 깔끔하면서도 모듈적인, 테스트하기 좋고 간결한 코드를 작성하는데 도움이 됩니다.

자바스크립트는 상태 관리를 개발자에게 떠넘기는 동적인 플랫폼이기에 생기는 문제점들을, 순수함수에 기반을 두고 이미 검증된 기법과 관례에 따라 구현하는 함수형으로 작성함으로써 해결 가능합니다.

 

1.2 함수형 프로그래밍이란?

 

FP 의 목표는 어플리케이션의 부수효과(side effect)를 방지하고 상태 변이(mutation of state)를 감소하기 위해 데이터의 제어흐름과 연산을 추상 하는 것.

 

예시)

document.querySelector('#msg').innerHTML = '<h1>Hello World</h1>';
// html 페이지에 id 가 msg 인 요소를 찾아 텍스트를 Hello World 로 지정

위 예시처럼 하드코딩한 프로그램은 메시지의 내용이나 형식을 바꾸는 동적인 변경이 불가능하며, 타깃 요소 또한 고정되어있습니다. 이 코드를 아래처럼 동적인 요소들만 매개변수로 변경해 함수로 작성한다면 재사용이 가능합니다.

function printMessage(elementId, format, message) {
	document.querySelector(`#${elementId}`).innerHTML = `<${format}>${message}</${format}>`;
}

printMessage('msg', 'h1', 'Hello World');

하지만 이 함수도 아직은 html 페이지에만 사용가능한 함수이며, 좀 더 함수형으로 변경을 하면 아래와 같습니다.

let printMessage = run(addToDom('msg'), h1, echo);

printMessage('Hello World');

위와 다르게 h1 이 스칼라값이 아닌, addToDom, echo 와 같은 함수로 들어가있습니다. 임시 함수인 run 은 addToDom / h1 / echo 함수를 체인처럼 연결해 한 함수의 리턴값이 다른 함수의 입력값으로 전달되게끔 합니다. 위의 경우엔, echo 함수가 'Hello World' 문자열을 반환해 h1 으로 전달되고, h1 함수의 리턴값이 addToDom 함수로 넘어가게 됩니다.

 

임시함수 run 링크

 

함수형 코드는 코드 변경을 쉽게 하기위해 코드 자체를 매개변수화 하기에 위와 같은 형태를 갖게 됩니다. 이런 작성을 통해 내부 로직은 수정없이 아래와 같은 작업도 가능하게 됩니다.

let printMessage = run(console.log, repeat(2), h2, echo);
// 메시지를 2회표시, 헤더는 h2 요소로, DOM 대신 콘솔에 출력

printMessage('Get Functional');

이런 방식을 이해하기 위해 다음의 개념을 숙지해야 합니다.

  • 선언적 프로그래밍
  • 순수함수
  • 참조 투명성
  • 불변성

1.2.1 함수형 프로그래밍은 선언적

 

자바, C#, C++ 등의 언어 - 구조적, 객체지향 언어가 지원하는 명령형 또는 절차적 모델 사용. 명령형 프로그램은 어떤 결과를 내기 위해 시스템이 상태를 변경하는 구문을 위에서 아래로 늘여놓은 수열이며, 간단한 예시인 아래와 같습니다.

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (let i = 0 ; i < array.length ; i++) {
	array[i] = Math.pow(array[i], 2);
}
array; // => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

이와 달리 선언적 프로그래밍은 프로그램의 서술부와 평가부를 분리해 제어 흐름이나 상태 변화를 특정하지 않고 프로그램의 로직이 무엇인지 표현식으로 나타냅니다. 예를 들면 위와 같은 동작을 하더라도 루프 카운터를 관리하고 배열 인덱스에 접근하는 일은 Array.map 에게 맡기는 방식입니다.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(num
    function(num) {
        return Math.pow(num, 2);
    }
}; // 이는 축약해서 쓰면 아래 코드와 동일하다.

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(num => Math.pow(num 2));
// ES6 의 람다표현식과 화살표 함수를 사용한 코드

루프를 제거하는 이유는, 재사용하기도 어려우며 다른 연산에 끼워 넣기 어려운 명령형 제어 구조물이기 때문입니다.

함수형 프로그래밍은 무상태성과 불변성을 지향하기에, 이처럼 부수효과와 상태 변이를 일으키지 않는 순수함수를 써야합니다.

 

1.2.2 순수함수와 부수효과

 

순수함수의 특성

  • 주어진 입력에만 의존할 뿐, 평가 도중 또는 호출 간 변경될 수 있는 숨겨진 값이나 외부 상태와 무관하게 작동
  • 전역 객체나 레퍼런스로 전달된 매개변수를 수정하는 등 함수 스코프 밖에서 어떠한 변경도 일으키지 않음
let counter = 0;
function increment() {
    return ++counter;
}

위 함수의 경우 자신의 스코프에 없는 외부 변수 counter 를 읽고 수정하므로 불순합니다. 일정한 결과값이 나오지않는 Date.now() 또한 순수함수가 아니며, this 키워드를 거쳐 인스턴스 데이터에 접근하는 것 역시 부수효과를 일으키는 예시이다. (물론 실무에서 이를 다 안쓰기란 어렵습니다..) 이외에도 아래처럼 부수효과가 발생하는 케이스는 다양합니다.

  • 전역 범위에서 변수, 속성, 자료구조를 변경
  • 함수의 원래 인수 값을 변경
  • 사용자 입력을 처리
  • 예외를 일으킨 해당 함수가 붙잡지 않고 그대로 예외를 던짐
  • 화면 또는 로그 파일에 출력
  • HTML 문서, 브라우저 쿠키, DB에 질의

이러한 경우를 실무에서 모두 근절할 순 없지만, 상태 변이를 최소화하고 관리할 수 있는 프레임워크를 제공하는 것이 목표입니다.

 

1.2.3. 참조 투명성과 치환성

 

참조 투명성이란, 어떤 함수가 동일한 입력을 받았을 떄 동일한 결과를 내는 것입니다.

1.
let counter = 0;
function increment() {
    return ++counter;
}

2.
let increment = counter => counter + 1;

1번의 함수를 2번처럼 작성함으로써 같은 입력에 같은 결과를 반환하는 안전한 함수로 변경할 수 있으며, 이런 함수는 코드를 테스트하기 쉽고 전체 로직을 파악하는 것도 쉽습니다.

 

1.2.4 불변 데이터 유지하기

 

다른 언어도 그렇듯이 문자열, 숫자 등 자바스크립트의 모든 기본형은 불변이지만, 배열이나 객체는 불변이 아니기에 함수 인수로 전달해도 원본이 변경되는 부수 효과가 발생할 수 있습니다.

let sortDesc = arr => {
    arr.sort(
        (a,b) => b - a
    );
};

위 코드는 Array.sort 가 원본 레퍼런스인 배열의 원소를 정렬하는 부수효과를 일으킵니다. 이는 언어 자체의 결함이기도 합니다. 이 점은 추후에 다시 수정하기로하고, 간명한 정의를 내리자면 함수형 프로그래밍이란 외부에서 관찰 가능한 부수효과가 제거된 불변 프로그램을 작성하기 위해 순수함수를 선언적으로 평가하는 것입니다.

 

1.3 함수형 프로그래밍의 좋은 점

 

함수형 인지력을 향상시키기 위한 핵심

  • 간단한 함수들로 작업을 분해
  • 흐름 체인으로 데이터를 처리
  • 리액티브 패러다임을 실현하여 이벤트 중심 코드의 복잡성을 주임

1.3.1. 복잡한 작업을 분해하도록 유도

 

모듈성의 단위, 곧 작업 단위함수 자신으로 바라봅니다. 여러 작업을 하는 덩어리의 코드를 하나로 묶지않고 하나의 작업만 하는 함수들로 분리하는 것입니다. 함수형으로 합성한 코드는 전체 표현식의 의미를 개별 조각에서 추론 가능하고, 함수 합성은 고수준의 추상화를 통해 내막을 밝히지 않은 채 모든 단계를 일목요연하게 나타냅니다.

 

1.3.2 데이터를 매끄럽게 체이닝하여 처리

 

과목을 수강한 학생들의 평균 점수를 계산하는 코드를 예시로 보겠습니다.

let enrollment = [
    [enrolled: 2, grade: 100],
    [enrolled: 2, grade: 80],
    [enrolled: 1, grade: 89]
];

let totalGrades = 0;
let totalStudetnsFound = 0;
for (let i = 0 ; i < enrollment.length ; i++) {
    let student = enrollment[i];
    if (student !== null) {
        if (student.enrolled > 1) {
            totalGrades += student.grade;
            totalStudentsFound++;
        }
    }
}
let average = totalGrades / totalStudentsFound // 90

명령형으로 짜면 이런 코드가 되겠지만, 아래처럼 로대시JS 라이브러리를 사용해 하나의 작업단위로 나눈 각 함수들을 묶고 필요한 시점까지 실행을 미루는 느긋한 평가를 수행할 수 있습니다. 이는 다른 함수형 언어에 기본 탑재된 필요시 호출 동작을 효과적으로 모방하며 CPU 부하를 줄일 수 있습니다.

_.chain(enrollment)
    .filter(student => student.enrolled > 1)
    .pluck('grade')
    .average()
    .value(); // 90  <-- _.value() 를 호출했을 때 연산들이 실행됨

 

1.3.3 복잡한 비동기 어플리케이션에서도 신속하게 반응

 

리액티브 프로그래밍은 함수형 프로그래밍 중에서도 가장 흥미로운 응용 분야이며, 자바스크립트 개발자는 이를 활용해 비동기코드, 이벤트 중심 코드의 복잡도를 줄일 수 있습니다. 가장 큰 장점은, 함수를 체인으로 묶고 합성하며 비즈니스 로직에만 집중할 수 있게 해준다는 점입니다.

리액티브 패러다임은 옵저버블이라는 장치를 사용해 움직이며, 아래 예시와 같습니다.

Rx.Observable.fromEvent(document.querySelector('#student-ssn'), 'keyup')
    .pluck('srcElement', 'value')
    .map(ssn => ssn.replace(/^\s*|\s*$|\-/g, ''))
    .filter(ssn => ssn !== null && ssn.length === 9)
    .subscribe(validSsn => {
        console.log(`올바른 SSN ${validSsn}!`);
    }
);

이 코드에선 모든 연산이 불변이고 비즈니스 로직은 개별 함수로 나뉘어졌다는 점이 주목할 부분입니다.

이처럼 리액티브와 함수형을 섞어서 쓰는 것을 함수형 리액티브 프로그래밍 라고 합니다.

 

1.4 Summary

 

  • 순수함수를 사용해 코드를 작성함으로써 전역상태를 깨트리지않고 테스트, 유지보수가 더 쉽도록 개발
  • 함수형 프로그래밍 + 람다 표현식을 조합해 가독성이 좋은 코드 개발
  • 여러 원소로 구성된 컬렉션 데이터는 map, reduce 같은 연산을 함수 체인으로 연결하여 처리
  • 함수를 기본적인 구성 요소로 취급하는 일급/고계함수 개념에 기반을 둔 함수형 프로그래밍으로 코드의 모듈성, 재사용성 향상
  • 함수형 프로그래밍 + 리액티브를 활용해 이벤트 기반 프로그램 복잡성 감소

 

 

출처 : 함수형 자바스크립트(책)