[개발 공부]

[읽고 정리하기] 함수형 자바스크립트 - 2장(1)

Narastro 2022. 2. 2. 18:28
이 글은
루이스 아텐시오, 『FUNCTIONAL PROGRAMMING IN JAVASCRIPT』, MANNING, 한빛미디어(2018)
를 읽고 이를 제 생각을 보태 정리한 글임을 밝힙니다.
책에서 그대로 발췌한 부분은 따로 인용구로 구분하겠습니다 :)

 

 

매주 챕터 하나씩 읽고 정리하려고 했는데

취준 때문에 이제서야 다시 시리즈를 시작하게 되었네요 ㅎㅎ

(취준기는 추후 포스팅하겠습니다!)

이제 갈 곳도 정해졌으니! 그간 미뤄왔던 공부들을 신나게(?) 해볼 차례네요!

그럼 바로 시작해보겠습니다.

 


Part 1. 함수형으로 사고하기

Chapter 2 - 고계 자바스크립트

이번 2장에서는 고계(고차) 자바스크립트에 대해 다뤄보겠습니다.

 

이 장에서는 

  • 자바스크립트가 함수형 언어로 적합한 이유
  • 자바스크립트는 다중 패러다임 개발이 가능한 언어
  • 불변성 및 변경에 대한 정책
  • 고계함수와 일급 함수
  • 클로저와 스코프의 개념
  • 클로저의 활용

을 다루고 있습니다.

 

여담이지만 저는 책을 읽을 때 목차를 먼저 쭉 읽는 것을 좋아하는데요.

목차를 읽고 저자가 어떻게 이야기를 풀어나갈지 나름대로 예상하고

예상과 내용을 비교하면서 읽다보면 흥미롭기도 하고 기억에도 더 오래 남는 느낌이 드는 것 같습니다.

그래서 저는 이 책 각 장 첫 페이지에 이처럼 이 장의 내용을 미리 보여주는 점이 좋았습니다.

 

2장은 객체지향과 함수형을 혼합한 하이브리드 언어인 자바스크립트를 빠르게 둘러봅니다.

...

자바스크립트가 함수형 언어로서 손색이 없는 이유와 그럼에도 미흡한 점을 위주로 설명합니다.
이와 함께 자바스크립트를 함수형 스타일로 개발할 수 있게 해주는 핵심 주역인 고계함수와 클로저도 등장합니다.

 


2.1 왜 자바스크립트인가?

 

바로 편재성(omnipresence, 어디에나 있음) 때문입니다.

 

자바스크립트는 가장 널리 쓰이는 언어 중 하나라고 저자는 말합니다. 모바일 애플리케이션, 웹사이트, 웹 서버, 데스크톱, 임베디드, DB에 이르기까지 응용 분야가 광대합니다. 특히 자바스크립트는 웹 세상을 대표하는 맹주 언어라는 점에서 폭넓게 쓰이는 FP 언어라고 볼 수 있습니다.

 

또한 리스프(Lisp), 스킴(Scheme)과 같은 함수형 언어의 영향을 받아 고계함수, 클로저, 배열 리터럴 등 FP 기법을 가지고 있습니다. 아직도 진화하고 꾸준히 개선 중이며, ES6의 화살표 함수, 상수, 이터레이터, 프로미스 등 함수형 프로그밍에 걸맞은 기능이 많이 추가되고 있습니다.

 

.

.

.

 

그럼에도 자바스크립트는 어디까지나 함수형인 동시에 객체지향 언어라는 사실을 명심해야 합니다.
안타깝게도 아직은 많은 개발자들이 함수형 스타일에 반하는 가변 연산, 명령식 제어 구조, 객체 인스턴스의 상태를 변경하는 코드를 아무렇지 않게 사용하고 있어서 진짜 함수형 자바스크립트 코드는 흔치 않습니다.
객체지향 언어로서의 자바스크립트를 먼저 살펴본 다음, 함수형 프로그래밍의 바다에 풍덩 뛰어들겠습니다.

 


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

객체지향과 함수형의 가장 중요한 차이점은 바로 데이터(객체 속성)와 기능(함수)을 조직하는 방법에 있습니다.

 

명령형 코드로 이루어진 객체지향 애플리케이션

  • 인스턴스 메서드를 통해 가변 상태를 노출하고 조작
  • 객체 기반의 캡슐화에 지나치게 의존하여 가변 상태의 무결성을 유지
  • 결국 응집도 높은 패키지가 형성

 

함수형 프로그램

  • 아주 단순한 자료형만을 대상으로 움직임
  • 데이터와 기능을 느슨하게 결합
  • 여러 자료형에 두루 적용 가능하고 굵게 나뉜 연산에 더 의존
  • 함수는 함수형 패러다임의 주된 추상화 형태

 

어려운 표현이 많네요. 책에 소개된 예시를 살펴보겠습니다.

 

자, 어떤 사람(Person)과 거주 국가가 같은 사람을 전부 찾고,
어떤 학생(Student_과 거주 국가와 다니는 학교가 모두 같은 학생을 전부 찾는 코드를 개발하고자 합니다.

 

객체지향 스타일 예시

// Person 클래스
peopleInSameCountry(friends) {
  var result = [];
  for (let idx in friends) {
    var friend = friends[idx];
    if (this.address.country === friend.address.country) {
      result.push(friend);
    }
  }
  return result;
};
// Student 클래스
studentsInSameCountryAndSchool(friends) {
  var closeFriends = super.peopleInSameCountry(friends);
  var result = [];
  for (let idx in closeFriends) {
    var friend = closeFriends[idx];
    if (friend.school === this. school) {
      result.push(friend);
    }
  }
  return result;
}

this와 super로 한 객체와 그 부모 객체가 단단히 결합된 코드가 됩니다.

super를 통해 부모 클래스에 접근하여 데이터를 받아오고 Student의 메서드로 같은 학교를 다니는 학생을 찾습니다.

this를 쓰게 되면 메서드 스코프 밖에 위치한 인스턴스 수준의 데이터에 접근할 수 있어서 부수효과를 야기할 수 있습니다. 이는 앞서 1장에서 설명한 대로 혼선을 일으킬 가능성이 큽니다.

 

 

함수형 스타일 예시

var selector = (country, school) =>
  (student) =>
    student.address.country === country &&
    student.school === school;
    
var findeStrudentsBy = (friends, selector) =>
  friends.filter(selector);
  
findStudentsBy([harry, joe, alan],
  selector('US', 'Princeton')
);

함수형 프로그래밍으로 작성하니 findStudentsBy와 같은 전혀 새로운 함수가 탄생했습니다.

이는 Person과 연관된 객체, school과 country를 조합한 객체를 받아 작동하는 함수입니다.

 

두 패러다임의 차이점

객체 지향은 데이터와 데이터 관계의 본질에 초점을 두는 반면,

함수형의 관심사는 해야 할 일, 즉 기능입니다.

스타일적으로도 함수형은 선언적인 반면, 객체지향형은 명령형 스타일이죠.

 

다음은 이를 보기 좋게 정리한 표입니다. (책에서 발췌했습니다.)

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

 

 1장에서도 설명하였지만, 객체지향을 비판하고자 하는 책이 아닙니다. 함수형 프로그래밍의 장점을 설득하고 객체지향과 함수형을 적절히 활용하면 강력한 애플리케이션을 구축할 수 있다고 저자는 말하고 있습니다. (애플리케이션을 구성하는 형식 간의 관계가 자연스러운 풍성한 도메인 모델과 이들 형식을 가지고 어떤 일을 할 수 있는 순수함수까지 거머쥐는 애플리케이션을 말함)

그리고 이 둘을 구분하는 선을 어디쯤에 그어야 할지는 우리에게 어느 패러다임이 더 편한지에 따라 결정하라고 합니다.

 


 

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

자바스크립트는 객체지향 + 함수형 언어이므로 함수형 자바스크립트로 개발할 때에는 특히 상태 변화 관리에 신경을 써야 합니다.

 

프로그램의 상태란 어느 한 시점에 찍은 모든 객체에 저장된 데이터의 스냅샷입니다.

 

안타깝게도 객체의 상태를 보호함에 있어서 자바스크립는 최악의 언어 중 하나라고 저자는 말합니다. 이는 앞서 살펴본 동적인 언어이기 때문인데, 언제든 속성을 추가, 삭제, 수정할 수 있기 때문입니다. 자칫 중대형 규모의 프로그램에서는 도저히 관리 안되는 코드로 발전할 소지가 있습니다.

 

자바스크립트를 함수형 언어로 쓸 경우 상태 관리는 아주 중차대한 문제입니다.
다음 절에서는 불변성을 관리하는 몇 가지 지침과 패턴을 살펴보겠지만,
데이터를 완전히 캡슐화하고 보호하는 건 여러분 스스로 훈련을 통해 엄격히 지켜야 할 목표입니다.

 


 

2.2.2 객체를 값으로 취급

 

1장에서 불변성을 바탕으로 사고하기 위해 사실상 모든 객체를 값으로 취급해야 한다고 하였습니다.

ES6에서 클래스와 관련된 간편한 구문들이 몇몇 추가되긴 했지만 자바스크립트에서 속성 값은 언제라도 바꿀 수 있습니다. 객체의 속성을 불변 상태로 고정시키는 장치가 어디 없을까요?

 

자바 final 키워드가 그렇지요. F# 같은 언어는 별도로 지정하지 않으면 불변 변수가 기본입니다.
자바스크립트에서는 이런 호사를 누릴 수 없습니다. 기본형 값은 변경할 수 없지만, 기본형을 가리키는 변수 상태가 바뀌니까요.
다른 언어를 적어도 흉내라도 내려면, 데이터를 불변 레퍼런스로 바라보게 해서 사용자 정의 객체도 마치 처음부터 불변이었던 것처럼 작동시킬 수 있어야 합니다.

 

ES6부터 추가된 상수 레퍼런스 const는 값을 재할당하거나 레퍼런스를 다시 선언할 수 없습니다.

외부 변수를 함수 호출 간에 예기치 않게 바뀌지 않도록 플랫폼 차원에서 특별한 의미를 부여한 것입니다.

그러나 const만으로는 FP가 요구하는 수준의 불변성을 실현하기 어렵다고 저자는 말합니다.

아시다시피 const로 선언한 객체의 내부 속성은 변경이 가능합니다.

 

const student = new Student('Alon','Church');

student.lastname = 'Mourning';  // 속성값 변경 가능

 

보다 엄격한 불변성 정책이 필요합니다.

캡슐화도 좋은 전략이고 클로저를 활용한 값 객체 패턴(value object pattern)도 괜찮은 방안입니다.

예시를 살펴보죠.

 

function zipCode(code, location) {
  let _code = code;
  let _location = location || '';
  
  return {
    code: function () {
      return _code;
    },
    location: function () {
      return _location;
    },
    toString: function () {
      return _code + '-' + _location;
    }
  };
}


const princetonZip = zipCode ('08544', '3345');
princetonZip.toString();  // '08544-3345'

 

값 객체는 오직 값에 따라 좌우되는 객체입니다. 일단 선언한 이후엔 그 상태가 절대 변하지 않죠.

메서드를 일부만 호출자에게 공개하고 _code, _location을 pseudo-private 변수처럼 다루는 객체 리터럴 인터페이스를 반환하는 식으로 내부 상태 접근을 차단할 수 있습니다.

이런 변수는 클로저를 거쳐야만 객체 리터럴에 접근할 수 있는데,

이는 뒤에서 다시 나오는 내용입니다.

 

여기서 toString 메서드는 순수함수가 아니지만 순수함수처럼 작동하면서 해당 객체를 순수한 문자열 형태로 나타냅니다.

값 객체는 함수형/객체지향 모두 가볍고 다루기 편합니다.

const와 함께 쓰면 문자열, 숫자와 의미가 유사한 객체를 생성할 수 있습니다.

 

값 객체는 함수형 프로그래밍의 영향을 받은 객체지향 디자인 패턴으로, 서로 다른 패러다임이 상호 보완적인 관계를 유지할 수 있음을 보여주는 또 다른 실례입니다.
값 객체는 이상적인 패턴이긴 하지만, 그래도 실세계의 문제를 전부 모형화하기엔 충분치 않습니다. 실무에서는 레거시 객체와 상호작용하거나 계층적 데이터를 처리하는 코드가 필요할 때가 생깁니다.
다행히 자바스크립트에서는 Object.freeze라는 멋진 방법이 있습니다.

 


 

2.2.3 가동부를 깊이 동결

이번 파트에서는 Object.freeze에 대해 설명하고 있습니다. 목차에서도 알 수 있듯이 가동부의 깊이를 동결하는 방법인 듯 하네요.

 

자바스크립트에서는 불변 필드를 표시하는 키워드는 없지만 writable처럼 숨겨진 메타속성을 제어하면 내부 조작이 가능합니다.

Object.freeze() 함수는 이러한 writable 속성을 false로 세팅해서 객체 상태를 못 바꾸게 동결합니다.

예시를 살펴봅시다.

 

const person = Object.freeze(new Person('harry',...));
person.firstname = 'Bob'  // 허용되지 않습니다.

위 코드를 실행하면 

TypeError: Cannot assign to read only property '_firstname' of #<Person>

이라는 에러가 납니다.

 

Object.freeze()는 상속한 속성까지 고정하므로 Student 인스턴스를 동결하면 이 객체의 프로토타입 체인을 따라가 Person이 물려준 속성 역시 모두 같은 방법으로 동결합니다.

단, 중첩된 객체 속성까지 동결하는 건 불가능합니다.

이처럼 내부 객체 속성까지 동결되지 않고 최상위 객체만 동결되는 것을 얕은 동결(shallow freeze)라고 합니다.

(ex. Person을 동결한다고 해도 내부의 person.address.country 값은 변경이 가능합니다.)

 

따라서 확실히 동결하고 싶을 땐 한 객체의 중첩 구조를 일일이 수작업으로 동결해야 합니다.

 

함수형으로 접근해서 객체의 불변 상태를 한곳에서 관리하는 렌즈(lense)라는 기법도 있습니다.

 


 

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

렌즈, 또는 함수형 레퍼런스라고 불리는 이 기법은 상태적 자료형의 속성에 접근하여 불변화하는 함수형 프로그래밍 기법입니다.
렌즈의 작동 방식은 카피 온 라이트와 비슷합니다. 상태를 관리하고 복사하는 방법을 알고 있는 내부 저장소 컴포넌트를 이용합니다.
여러분이 렌즈를 직접 구현할 필요는 없고, 람다JS라는 자바스크립트 라이브러리를 쓰면 됩니다.
기본적으로 람다JS는 전역 객체 R로 모든 기능을 노출합니다.
앞서 소개한 Person의 lastname 속성은 R.lensProp을 써서 다음과 같이 렌즈로 감싸면 됩니다.

 

var person = new Person(...);
var lastnameLens = R.lenseProp('lastname');
const newPerson = R.set(lastnameLens, 'Mourgin', person);

 

읽는 것은 R.view, 세터는 R.set으로 동작합니다.

R.set을 호출하면 원래 객체 상태는 그대로 유지한 채 새로운 값이 포함된 객체 사본을 새로 만들어 반환합니다.

 

...

 

이 밖에도 책에는 람다JS에 대한 설명이 나와있으나, 본질에 집중하고자 이만 줄이겠습니다.

 

이처럼 필드에 접근하는 로직을 객체로부터 분리하여 this에 의존할 일을 없애고 어떤 객체라도 그 내용물에 접근하여 조작할 수 잇는 강력한 함수를 내어주겠다는 FP 철학과 잘 어울립니다.

 

이 다음 장은 애플리케이션을 움직이는 원동력이자, 함수형 프로그매잉의 요체인 함수에 대한 내용을 담고 있습니다.

글이 너무 길어지는 것 같아서 여기서 줄이고 다음 포스팅에서 마저 2장을 살펴보도록 하겠습니다 :)

 

 

번역본이라서 그런지 중간중간 어투가 어색한 부분이 있습니다. 이를테면 2.2.3의 가동부를 깊이 동결... 대략 느낌은 오는데 어색한 표현이죠. 그런 점을 감안하고 보더라도 글의 흐름이 있어서 이해하기는 어렵지 않은 것 같습니다. ㅎㅎ

 

읽어주셔서 감사합니다😊