본문 바로가기

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

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

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

 

Chapter 2. 고계 자바스크립트

 

" 자연어는 지배적인 패러다임이 없습니다. 자바스크립트도 마찬가지입니다. 개발자들은 절차적, 함수형, 객체지향형 접근 방법이 들어 있는 손가방에서 적절히 골라 섞어 쓰면 됩니다."

 

이번 장에서는 자바스크립트가 함수형 언어로서 손색이 없는 이유 및 미흡한 점과 함께 고계함수, 클로저까지 보도록 하겠습니다.

 

2.1 왜 자바스크립트인가?

 

자바스크립트의 장점은 편재성 (어디에나 있음) 입니다. 동적 형식이고 객체지향적 범용 언어이자 가장 널리 쓰이기 때문입니다. 자바스크립트 구문은 C 언어와 비슷하지만 리스프(Lisp), 스킴(Scheme) 같은 함수형 언어의 영향을 많이 받았습니다. 리스프, 스킴의 공통점인 고계함수, 클로저, 배열 리터럴 등은 자바스크립트가 FP 기법을 활용할 수 있는 기반이 되었습니다. 자바스크립트 함수는 주요 작업 단위로서 애플리케이션에게 할 일을 시키는 역할뿐만 아니라 객체 정의, 모듈 생성, 이번트 처리 등의 책임도 맡고 있습니다.

 

2.2 함수형 대 객체지향 프로그래밍

 

함수형 / 객체지향 프로그래밍 모두 중대형 시스템 개발에 사용 가능하며, 스칼라, F# 같은 하이브리드 언어는 이 두 패러다임을 잘 섞어놓았으며, 자바스크립트로도 가능합니다. 다만 어느정도로 조합해서 사용할지는 개발자의 개인적인 취향 혹은 해결해야 할 문제의 요건에 따라 달라지며, 함수형 / 객체지향 두 접근 방법이 어떤 차이점이 있는지 살펴보도록 하겠습니다.

 

객체지향 프로그램의 핵심은 새로운 파생 객체를 생성하여 코드를 재사용할 수 있게 하는것입니다. 하지만 이러한 경우 모든 하위형에 적용할 필요가 없는 기존 객체에 추가할 때 문제가 될 수 있습니다. 이처럼 객체지향과 함수형의 가장 큰 차이점은 데이터 (객체속성) 와 기능 (함수) 을 조직하는 방법에 있습니다. 객체지향 어플리케이션은 인스턴스 메서드를 통해 가변 상태를 노출하고 조작할수 있도록 객체 기반의 캡슐화에 의존해 가변 상태의 무결성을 유지합니다. 반면 함수형 프로그램은 호출자 (caller) 로부터 데이터를 숨길 필요 없이 소규모의, 단순한 자료형만을 대상으로 움직입니다. 함수형 코드는 잘게나뉜 인스턴스 메서드 대신 여러 자료형에 두루 적용 가능하고 굵게 나뉜 연산에 의존합니다.

이 그래프처럼 함수형과 객체지향의 교차점에서 두 패러다임을 잘 활용하면, 객체를 불변 객체나 불변 값으로 바라보고 기능을 함수로 분리하여 객체 내에서 작동되게끔 짠 코드가 될 수 있습니다. 예시를 들어보겠습니다.

get fullname() {
  return [this._firstname, this._lastname].join(' ');
} // 객체지향

const fullname =
  person => [person.firstname, person.lastname].join(' '); // 함수형

이미 Person 이라는 객체가 있고 객체 내부에 Person 의 fullname 을 얻어오는 메서드를 작성할때, 첫번째 메서드보단 두번째 메서드처럼  함수형에서 가급적 사용하지않는 this 를 제거하고 객체를 명시적인 매개변수로 전달해 부수효과를 제거할 수 있습니다.

 

자바스크립트는 객체지향 + 함수형 언어이므로 함수형 자바스크립트로 개발할 때에는 상태 변화 관리에 신경을 써야하며, 객체지향과 함수형 프로그래밍의 중요한 특징을 아래 표로 보겠습니다.

  함수형 객체지향형
합성단위 함수 객체 (클래스)
프로그래밍 스타일 선언적 명령형
데이터와 기능 독립적인 순수함수가 느슨하게 결합 클래스 안에서 메서드와 단단히 결합
상태 관리 객체를 불변 값으로 취급 인스턴스 메서드를 통해 객체를 변이시킴
제어 흐름 함수와 재귀 루프와 조건 분기
스레드 안전  동시성 프로그래밍 가능 캡슐화하기 어려움
캡슐화 모든 것이 불변이라 필요 없음 데이터 무결성을 지키기 위해 필요함

 

2.2.1 자바스크립트 객체의 상태 관리

 

객체 상태를 보호하는 측면에서 자바스크립트는 최악의 언어 중 하나입니다. 자바스크립트 객체는 동적이라 언제건 속성을 추가, 삭제, 수정할 수 있습니다.

 

2.2.2 객체를 값으로 취급

 

함수형 프로그래밍에선 불변성을 바탕으로 사고하기 위해 모든 객체를 값으로 취급해야 합니다. 하지만 자바스크립트에서 객체의 속성 값은 언제라도 바꿀 수 있으며, 자바의 final 키워드, F# 처럼 별도로 지정하지 않으면 불변 변수가 기본이 되는 이러한 장치가 필요합니다. 일반적인 상수는 const 키워드로 선언함으로서 불변성을 실현할 수 있습니다. 객체의 경우, 변이를 방지할 목적으로 캡슐화 또는 객체 구조가 단순하면 값 객체 패턴도 괜찮은 방법입니다. 아래 예시)

function zipCode(code, location) {
  let _code = code;
  
  return {
    code: function () {
      return _code;
    }
  };
}
// 메서드를 일부만 호출자에 공개하고, _code 를 private 변수처럼 다루는 객체 리터럴 인터페이스를 반환하는 식으로 내부 상태 접근을 차단

혹은 이런 방법도 가능합니다. 아래 예시)

function coordinate(lat, long) {
  let _lat = lat;
  let _long = long;
  
  return {
    translate: function (dx, dy) {
      return coordinate(_lat + dx, _long + dy);
    }
  };
} // translate 처럼 사본을 새로 만들어서 반환하는 메서드 또한 불변성을 구현하는 또 다른 수단

하지만 이런 방법은 실무에서 적용하기 충분하지 않으며, Object.freeze 라는 방법을 주로 사용합니다.

 

2.2.3 가동부를 깊이 동결

 

const person = Object.freeze(new Person('Haskell', 'Curry', '444-44-4444'));
person.firstname = 'Bob';
// Object.freeze 로 동결시켰기때문에 firstname 변경을 시도하면 에러가 발생

Object.freeze() 는 상속한 속성까지 고정하므로 위처럼 객체를 동결시킬 수 있지만, 만약 객체의 속성에 내부 객체가 또 있다면 이는 동결되지 않으며, 이를 얕은 동결 (shallow freeze) 라고 합니다. 따라서 Object.freeze() 를 사용해 확실히 동결하려면, 객체의 중첩 구조를 일일이 수작업으로 동결해야 합니다.

 

2.2.4 객체 그래프를 렌즈로 탐색 / 수정

 

위와 같은 방법들을 사용하다간 오히려 동결시키기 위한 코드가 더 필요한 상황이기에, 함수형으로 접근해서 객체의 불변 상태를 한곳에서 관리하는 렌즈 라는 기법을 사용할 수도 있습니다. 렌즈 (lense) 또는 함수형 레퍼런스 (functional reference) 라고 불리는 이 기법은 상태적 자료형의 속성에 접근하여 불변화 하는 함수형 프로그래밍 기법이며, 직접 렌즈를 구현할 필요없이 람다JS 라이브러리를 사용하면 됩니다. 아래 예시)

const person = new Person('Alonzo', 'Church', '444-44-4444');
// Church 가 lastname 이다.
const lastnameLens = R.lenseProp('lastname');
// lastname 속성 R.lensProp 을 사용해 렌즈로 감음
const newPerson = R.set(lastnameLens, 'Mourning', person);
// R.set 을 호출하면 원래 객체 상태는 그대로 유지한 채 새로운 값이 포함된 객체 사본을 새로 만들어 반환

newPerson.lastname; // -> 'Mourning'
person.lastname; // 'Church'

또한 렌즈는, Object.freeze() 와 달리 중첩 속성의 불변화까지 지원합니다.

 

2.3 함수

 

함수 (function) 는 () 연산자를 적용하여 평가할 수 있는 모든 호출 가능 표현식입니다. FP의 함수는 수학책에 나오는 함수처럼 null 이나 undefined 가 아닌 사용 가능한 결과를 낼 경우에만 유의미하며, 그 외에는 외부 데이터 변경 등의 부수효과를 일으킨다고 볼 수 있습니다.

 

2.3.1 함수를 일급 시민으로

 

일급 함수란 함수를 값으로 다룰 수 있는 것이며 함수 스스로를 객체 취급하는 것인데, 자바스크립트 함수는 실제로 객체이기 때문에 일급이며 일급 시민이라고도 합니다. 함수를 정의하는 방법은 여러가지가 있지만 아래처럼 생성자를 통해 함수를 인스턴스화하는 방법도 있습니다.

const multiplier = new Function('a', 'b', 'return a * b');
multiplier(2,3); // 6

 

자바스크립트 함수는 모두 Function 형식의 인스턴스이며, 이는 자바스크립트 함수의 일급 자질을 보여주는 부분입니다.

 

2.3.2 고계함수

 

고계함수란 함수 인수로 전달하거나 함수를 반환받을 수 있는 것을 말합니다. 아래 예시)

function applyOperation(a, b, opt) {
  return opt(a,b);
}

const multiplier = (a, b) => a * b;

applyOperation(2, 3, multiplier); // 6

// applyOperation 함수는 multiplier 라는 함수를 인수로 받고 있습니다.

자바스크립트 함수는 일급 + 고계이며, 자신이 받은 입력값을 기반으로 정의된 언젠가는 실행될 값입니다. 함수형 프로그래밍에선 이러한 점을 활용해 코드를 작성하는 방향으로 사고해야 합니다.

 

2.3.3 함수 호출 유형

 

자바스크립트 함수는 호출 시점의 런타임 컨텍스트, 즉 함수 내부의 this 값을 자유롭게 지정할 수 있으며 호출하는 방법도 아래처럼 다양합니다.

  • 전역함수로 호출 : this 레퍼런스는 전역 객체, 또는 undefined 를 가리킵니다.
  • 메서드로 호출 : this 레퍼런스는 해당 메서드를 소유한 객체입니다.
  • 앞에 new 를 붙여 생성자로 호출 : 새로 만든 객체의 레퍼런스를 암시적으로 반환합니다.

이런 특성 탓에 정말 이해하기 어려운 코드가 될 수 있으며, 함수형에서는 이러한 this 를 사용할 일이 거의 없습니다.

 

2.4 클로저와 스코프

 

자바스크립트 전에는 클로저는 순수 FP 언어에만 존재했고 특정 어플리케이션에서 제한적으로 사용됐습니다. 위의 2.2.2 에서 예시로 든 코드 또한 클로저를 활용한 코드입니다. 클로저(Closure)는 함수를 선언할 당시의 환경에 함수를 묶어둔 자료구조입니다. 함수 선언부의 물리적 위치에 의존하므로 정적 스코프 또는 어휘 스코프 라고도 합니다. 함수가 자신을 둘러싼 주변 상태에 접근할 수 있기 때문에 클로저를 이용하면 명확하고 가독성 높은 코드를 작성할 수 있습니다. 또한 고계함수를 응용한 함수형 프로그래밍, 이벤트 처리 및 콜백, 프라이빗 변수 모방의 용도로도 사용할 수 있습니다.

 

2.4.1 전역 스코프의 문제점

 

전역 스코프는 가장 단순하면서, 가장 나쁜 스코프입니다. 전역 스코프에는 스크립트 최외각에 선언된 어느 함수에도 속하지 않은 객체 및 변수가 자리하고, 자바스크립트 코드가 자유롭게 접근할 수 있습니다. 전역 변수는 페이지에 적재된 모든 스크립트가 공유하기 때문에 모듈 단위로 코드를 묶어두지 않으면 이름공간이 충돌할 수 있으며, 이로인해 다른 파일에서 선언된 변수, 함수를 재정의해야하는 문제가 발생할 수 있습니다. (FP 에선 삼가해야할 부분입니다)

 

2.4.2 자바스크립트의 함수 스코프

 

함수 스코프는 자바스크립트가 선호하는 스코프 방식입니다. 함수 내부에 선언된 변수는 모두 해당 함수의 지역 변수라 다른 곳에서는 안보이고, 함수가 반환되는 시점에 사라집니다.

 

2.4.3 의사 블록 스코프

 

중괄호 {} 로 감싼 블록 수준의 스코프를 말합니다. 이부분에선 자바스크립트의 호이스팅 이라는 특징 때문에 보통의 블록 스코프와 혼란스러울 때가 있었지만, ES6 부터는 let 키워드로 어느정도 극복이 가능합니다.

 

2.4.4 클로저 응용

 

  • 프라이빗 변수를 모방
  • 서버 측 비동기 호출
  • 가상의 블록 스코프 변수를 생성

자바스크립트에는 private 와 같은 접근자가 없기 때문에, 불변성을 지키기 위한 변수에 접근 자체를 할 수 없게 만들 캡슐화가 필요합니다. 클로저를 활용해 이 점이 가능하며, 자바스크립트 라이브러리나 모듈 개발자는 전체 모듈의 프라이빗 메서드와 데이터를 숨길 때 클로저를 활용합니다. 이것을 모듈 패턴이라고 하며, 내부 변수를 캡슐화하면서 전역 레퍼런스 개수를 줄이고 외부 세계에는 딱 필요한 기능만 표출하기위해 즉시 실행 함수 (IIFE) 를 사용합니다.

 

2.5 Summary

 

  • 자바스크립트는 OOP 와 FP 양쪽 다 가능한 언어
  • OOP 에 불변성을 도입하면 함수형 프로그래밍을 섞어서 사용 가능
  • 고계/일급 함수는 함수형 자바스크립트를 구사하는 근간
  • 클로저는 정보 감춤, 모듈 개발 뿐만 아니라 여러 자료형에 걸쳐 굵게 나뉜 함수에 원하는 기능을 매개 변수로 넘기는 등 여러 쓰임 가능

 

 

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