Anjana Vakil: 함수형 JS를 위한 불변 자료구조 (JSConf EU, 2017)

Young
BedroomConf Korea
Published in
20 min readMay 8, 2021
  • Youtube 영상 / 발표 슬라이드 (다른 컨퍼런스에서 사용한 슬라이드라 내용이 살짝 다릅니다.)
  • 이 글은 2017년 JSConfEU에서 진행한 발표 영상의 영어 자막을 토대로 한글 번역한 것입니다. 함수형 프로그래밍 및 불변 자료 구조의 이해에 도움이 되시길 바랍니다. 발표자의 허락을 받아 게시합니다.
Photo by Mike Koss on Unsplash

모두들 안녕하세요. 다들 카페인 충전은 완료하셨죠? 제 이름은 안쟈나 바킬(Anjana Vakil)입니다. 이름과 동일한 트위터 계정이 있으니 팔로우 하실 분은 신청 부탁드려요. 오늘의 주제는 자바스크립트 함수형 프로그래밍에서 사용하는 불변 자료구조(immutable data structure)입니다. 불변 자료구조가 무엇인지, 왜 멋진 것인지 설명해보고 여러분은 자바스크립트를 좋아한다고 들었으니, JS 코드 적용까지 해보겠습니다.

제 소개를 먼저 하겠습니다. 아마 여기서 유일하게 웹 개발자가 아닌 사람인 것 같아요. 현재 ÜberResearch 에서 엔지니어로 근무하고 있습니다. 동료들과 과학 연구 자금 분야 데이터용 사용자 정의 질의어(custom query language)를 개발하고 있어요. 멋진 뉴욕시 프로그래밍 커뮤니티인 Recurse Center의 동문이기도 합니다. 또한 아웃리치(Outreach) 프로그램의 동문이기도 해요. 아웃리치 프로그램에 대해 들어본 적이 없는 분들을 위해 설명하자면, 여성을 비롯한 사회적 소수자 분들의 오픈소스 프로젝트 참여를 끌어내기 위해 모질라 같은 단체에서 유급 인턴십을 할 기회를 제공하는 프로그램입니다. 그러니 저도 모질라 커뮤니티 멤버인 셈이죠. 이런 프로그램이 궁금하다면 오늘 발표 후에 같이 기쁘게 이야기 나눌 의향이 있습니다.

불변 데이터와 바위의 멋짐에 대하여

아마 제가 함수형 프로그래밍(이하 FP로도 표기) 좋아한다는 것도 아실듯해요. 함수형 프로그래밍은 정말 최고예요(“it rocks” —역주: rock으로 말장난을 해서 일부러 씁니다). 동의하시는 분 있나요?

from giphy

사이드 이펙트를 피하려면

FP는 다른 프로그래밍 패러다임, 명령형이나 객체 지향형 등에서 발생하는 골치 아픈 문제들을 해결하기 아주 좋은 방법이죠. 순수 함수 프로그램들이 하는 일은 인풋을 아웃풋으로 변환하는 것이 전부입니다. 콘솔 출력이나 전역 상태 변경(mutate) 등의 사이드 이펙트(side effect)도 없어요. 함수는 순수하게 데이터가 들어가고 나가는 곳이 됩니다. 데이터 트랜스포머의 역할을 하는 것이죠. 사이드 이펙트를 피하려면 중요한 것은 불변 데이터를 쓰는 것입니다. 이들은 한번 만들면 절대 변경할 수 없습니다. 실수로 함수에서 뭔가를 바꾸는 사고를 방지할 수 있죠. 처음부터 불변이라고 정해 두었으니까요. 불변성(immutability)은 FP와 더불어 아주 멋져요. 왜 멋진지는 이따가 알아보기로 해요.

멋짐에 대해 이야기하는 김에, 바위(rock)에 대해 이야기해 볼까요. 이 그림은 바위입니다. 멋지죠? 불변성도 똑같아요. 동의 하실지 모르겠는데, 제가 요즘에 테크 콘퍼런스에 많이 다녀봤는데 다들 시적인 감수성이 부족하다고 느끼고 있어요. 그러니 시를 한 편 읽어드리고 싶어요.

“아무도 이 바위가 앉아 있는 것처럼 앉지 않는다. 너, 바위야, 멋지구나. 바위는 그저 앉아있고 그렇게 있다. 바위는 그렇게 앉아 있는 법을 알려주는구나. 우리가 배워야 할 것이 그거야.”

from yarn

참으로 맞는 말이고, 정말 심오합니다. (청중 박수) 감사합… 아 저에게 감사하지 말고 “아이 하트 헉커비스 (I Heart Huckabees, 2004 — 미국 코미디 영화)”에 감사해 주세요. 굉장한 영화인데 여기서 나온 시입니다. 한번 봐보세요. 불변 데이터가 멋진 점이 바로 이겁니다. 마냥 앉아 있어요. 한번 만들면 절대 변하지 않기 때문에, 변화로 인한 골치 아픈 문제들이 생기지 않으니 끝내주죠.

동물원 만들기

배열에 외계인을 넣어보자

변성(mutability)과 관련되어 발생할 수 있는 문제들을 먼저 살펴볼까요? foo라고 불리는 배열이 하나 있다고 가정해 볼게요. 숫자도 좀 넣어볼게요. 흠, 벌써 재미없어요. 더 재미나게 만들어 볼게요. 동물원(zoo) 배열이 있고 동물을 넣을 거에요, 재밌죠? 그런데 뭔가 변화를 주고 싶어요. 토끼를 좀 더 이국적인 것으로 바꾸고 싶어요. 외계인 등장! 멋지네요. 한층 더 이국적인 동물원을 가지게 되어 기쁩니다. 한 일이라곤 배열 속 자그마한 셀 하나를 바꾼 것 뿐인데, 이제 제 동물원에는 외계인이 삽니다.

누가 내 동물원에 외계인을 넣었지?

제가 보기에는 꽤 만족스러운데, 동료가 와서 동물원에는 지구에 사는 생명체나 동물이 올 것을 기대했다고 하네요. 외계인은 들어오면 안된다고 딴지를 겁니다.

“누가 외계인을 넣은 거야? 프로그램이 동작하지 않잖아. 누가 그런 거야?”
— 성난 동료

이런 걸 보면 변성에서 야기되는 문제가 몇 개 보입니다. 누가, 무엇을, 언제 바꾼 것인지 변경 사항을 관리해야 해요. 동물원에 어떤 동물을 누가 넣었는지 추적해야 하죠. 비용이 너무 많이 듭니다. 개인이나 팀이나 관리 주체에 상관없이 힘들어요. 그리고 제 동료는 지구의 생명체만 동물원에 넣기로 계획을 세우고 외계인 같은 우주 생명체를 다룰 방법은 마련하지 않았기 때문에 코드에 버그가 생길 거에요. 이런 것들이 골치 아픈 사이드 이펙트 입니다.

한번 만든 동물원은 바꿀 수 없게 제한을 주면 어떨까요? 만약 더 이국적이고 새로운 동물원을 만들고 싶다면 이미 존재하는 동물원과 같은 크기의 배열을 복사해야 합니다. 토끼를 꺼내고 외계인을 넣는 등의 변화를 주고 싶다면, 복사본을 새로 만들어 변경하는 방법밖에 없습니다. 꽤 괜찮은 방법이에요. 이걸 보면 제 동료는 프로그램에 버그도 없고 자기 동물원에 지구 생명체밖에 없는 것을 보고 기뻐할 것이기 때문이죠. 그렇지만 여전히 배열 전체를 복사해야 하는 문제점이 있어요. 바뀐 것도 별로 없는데 메모리를 전체 배열 크기만큼 다시 할당하고 모든 요소를 복사해야 해요. 코드 실행 속도가 느려지게 됩니다. 메모리도 많이 들어요. 공간과 시간 소모가 커집니다. 복사는 이 둘을 낭비하여 복잡도를 올릴 염려가 있습니다.

Immutable vs. Persistent, Partially Persistent vs. Fully Persistent

슬퍼지네요. 그러긴 싫은데! 불변성을 다룰 더 나은 방법을 찾아야 해요. 다행히도 엄청 똑똑한 분들이 열심히 노력했고, 그 결과 매우 좋은 해결 방법을 몇 가지 제시했어요. 바로 불변 데이터 구조(immutable data structure)를 사용하는 것입니다! 함수형 프로그래밍 분야나 이 자료구조가 실제로 활용되고 있는 리액트를 살펴봤다면 들어봤을 법한 용어일 거예요. 정의대로 불변 자료 구조는 바위와도 같아서 한번 만들면 그냥 쭉 있습니다. 절대 바뀌지 않아요. 지속적인 데이터 구조(persistent data structure)라는 용어도 있습니다. 때때로 두 용어가 같은 의미로 사용되기도 하는데 살짝 의미가 다릅니다. 불변 데이터는 절대 바뀌지 않는 데이터 이지만 지속적인 데이터는 이전 버전에 접근 가능한 데이터 입니다. 수정해서 새로운 버전을 만들어도 옛날 버전 역시 보존합니다.

부분 지속적 데이터 구조(partially persistent data structures)라는 용어도 아마 들어보셨을 거에요. 이전 버전을 보존 및 접근할 수 있으나, 그 버전으로 되돌리거나 수정할 수는 없습니다. 그리고 완전 지속적 데이터 구조(fully persistent data structure)라는 것도 있는데, 이를 사용하면 시간 여행이 가능해집니다. 이전 버전으로 되돌아가서 그 버전을 업데이트 할 수 있어요. 자, 여기까지 듣고 나니 git과 같은 버전 컨트롤이 생각나지 않나요? 같은 아이디어에서 비롯된 거라고 할 수 있어요.

효율적인 데이터 버저닝

이제부터는 지속 불변 데이터 구조(persistent immutable data structure)에 대해 이야기해보도록 하겠습니다. 말 그대로 지속적이고(persistent), 불변적(immutability)인 자료구조입니다. 최초에 토끼가 있던 저의 동물원처럼 원본 데이터를 보존하는 것이 핵심입니다. 원본을 바위처럼 그대로 두되, 새로운 버전을 효율적으로 생성하는 것이 목표입니다. 마술적인 속임수를 써야 할까요? 공간과 시간 복잡도의 신에게 그렇게 해달라고비는 제사를 지내야 할까요? 기원의 춤을 춰볼까요? 안 그래도 됩니다.

춤을 춰볼까요?

Tree & Sharing

사실 매우 간단해요. 트리(tree)와 공유(sharing)면 됩니다. 좋죠? 간단한 두 가지 개념으로 불변 데이터를 효율적이게 사용할 수 있습니다. 어떻게요? 자, 트리 이야기를 해볼게요. 트리도 상당히 멋지기 때문이죠. 참, 죄송하지만 트리에 대한 시는 찾지 못했습니다. 동물원 배열을 트리 구조로 표현하는 걸 상상해 보세요. 동물(혹은 값) 전부를 트리의 개별 잎으로 만들 수 있겠네요. 그렇지만 동물들이 외로워하니 짝꿍을 지어주도록 할게요. 이제 각각의 잎 마다 동물이 두 마리씩 있네요. 짝끼리 잘 어울리기를 바랍니다. 서로 잡아먹으면 안될텐데… 6번 호랑이가 코알라를 잡아먹지 않으면 좋겠어요. 그러면 이제 두 개의 잎을 연결해 중간 노드를 만들 수 있고, 이들을 계속 연결하면 모든 구조를 연결하는 루트에 다다릅니다. 이전에 배열로 표현한 구조가 트리로 바뀌었네요. 트리 동물원이 완성되었습니다.

슬라이드(참고: 여기서는 1번 노드에 토끼 대신 코알라가 들어있네요)

경로 복사와 구조적 변경

값 변경하기

값을 어떻게 변경하죠? 데이터를 바꿀 수 없다면 외계인은 어떻게 넣을까 싶어요. 우선 변경 대상 노드를 선택합니다. 화면 하단에 있는 노드 중에 0/1 노드가 되겠네요. 그리고 복사본을 새로 만듭니다. 원숭이는 그대로 두고 토끼 대신 외계인을 넣습니다. 이제 이 복사본 노드를 가르키는 중간 노드 역시 복사본을 만들어야 합니다. 새로 만든 중간 노드를 또 위의 노드 복사본에 연결하고, 그런 식으로 계속해 나가면 루트 노드도 새로 만들게 됩니다. 루트까지 갔다면 데이터 구조가 새로 만들어진 셈입니다. 바뀐 잎부터 노드까지 경로를 복사해가며 업데이트하는 기법을 경로 복사(path copying)라고 합니다.

슬라이드: 새로운 트리(초록색)과 원본 트리(녹색). 두 트리 사이에 공유되는 노드는 자주색으로 표현했습니다.

전체 배열을 다시 복사할 필요가 없으니 아주 좋네요. 변경된 잎부터 루트까지 가는 경로에 있는 노드들만 새로 만들면 되는 것입니다. 선형적이었던 구조를 로그 적(logarithmic)으로 바꿨어요. 성능도 더 좋습니다. 자주색으로 칠해진 노드들, 즉 트리의 대부분은 옛날 버전과 새로운 버전 사이에 공유가 되므로 공간도 많이 아낄 수 있습니다. 원본 트리에서 변경되지 않은 부분은 재활용하면 되니까요. 이전 방법에서는 메모리를 엄청나게 낭비했다면, 이제는 많이 절약할 수 있습니다. 이전과 이후 두 버전 사이의 트리 구조가 공유되기 때문에 이를 구조적 변경(structural changing)이라고 합니다.

값 얻기

업데이트를 살펴봤는데, 데이터 구조에서 값은 어떻게 얻으면 될까요? 사실, 이 트리는 그냥 트리가 아니라 특별한 트리입니다. 트리 트리(TRIE tree)라고 부릅니다. “TRIE”는 “Retrieval(검색)”이라는 단어에서 유래합니다. 이름이 좀 헷갈리는데, 괜찮다면 트리(tree)와의 구분을 위해 “트라이(tries)”라고 하겠습니다. 트라이에서는 잎으로 값을 표현하고, 루트에서 값까지 도달하는 경로로 데이터와 관련된 키를 표현합니다. TRIE는 대부분 단어 같은 문자를 키로 사용하여 값을 저장합니다. 예를 들어 여기 이 트라이에서 ‘tea’ 키의 값을 얻으려면 한 번에 한 글자씩 움직여서 ‘t’ 다음에 ‘e’, 그리고 ‘a’로 갑니다. ‘tea’ 까지 가니 3이라는 값을 얻었어요. (의도한 것은 아니지만 아까부터 계속 ‘ee’로 끝나는 단어를 말하고 있네요.)

슬라이드: 이 TRIE tree에서는 ‘ape’ (유인원) 키를 통해 고릴라 이모지를 얻습니다.

인덱스를 키로 사용하기

멋져 보이는데, 배열과 같은 구조에서는 단어 말고 인덱스를 키로 써야 하지 않을까요? 인덱스를 이진수로 변환하면 단어처럼 취급할 수 있다는 점을 생각해 봐야 합니다. 각 비트를 하나의 글자로 생각하면 비트 단위로 트리를 따라 내려가는 것이죠. 그럼 동작을 한번 보겠습니다. 배열의 다섯 번째 항목을 얻어 볼게요. 5번 인덱스에 있는 동물이 뭔지 볼 거예요. 5를 이진수로 변환하면 101이 됩니다. 101을 단어처럼 취급해서 단계를 밟아나가면 됩니다. 글자 하나씩, 즉 비트 하나씩 나아갈게요. 가지의 루트에서 출발합니다. 루트에서는 0, 또는 1 가지를 선택할 수 있네요. 1을 택합니다. 그다음에는 0 가지를 택하고, 다음에는 1 가지를 택하면 되겠네요. 1, 0, 1 이렇게 나아가면 5번 인덱스의 개구리와 마주하게 됩니다. 상당히 간단해 보이는 아이디어인데, 굉장히 파급력 있습니다. 트리 횡단 속도가 빨라지니 불변 데이터 구조의 복사본을 새로 만들더라도 구조 공유가 효율적으로 이루어지기 때문입니다.

슬라이드: 숫자 5를 이진수 101로 변환하여 1, 0, 1 순서대로 밟아갑니다. (오렌지색 선)

그리고 알아두셔야 할 점은, 꼭 노드마다 가지가 두 개 파생되는 이진 트리를 사용하지 않아도 된다는 것입니다. 발표 슬라이드에 넣기에는 이진 트리가 딱 적당한데, 실은 32갈래(32-way branching) 트리가 제일 많이 사용됩니다. 지금 보고 있는 트리는 레벨당 1bit의 정보를 담고 있습니다. 그리고 내려갈 때마다 1bit 씩 살펴보죠. 그러나 32갈래 트리를 사용한다면 각 레벨 당 5bit의 정보를 표현할 수 있습니다. 그러면 이런 트리를 사용하는 거겠죠. 18,977과 같이 훨씬 큰 숫자를 이진수로 표현한다면 0과 1을 많이 써야합니다. 이진 트리로 하면 15레벨이 넘는 정말로 깊은 트리가 만들어질 거에요. 너무 방대하고 길어요. 레벨 당 가짓수를 32갈래로 늘리면 5bit 단위로 나눌 수 있으니까 3레벨만 거치면 됩니다.

트리의 깊이와 레벨 당 노드 개수 사이의 균형점을 고려해야 합니다. 만약 1bit의 정보만 표현하는 상황이라면 노드가 매우 적겠죠. 이런 경우라면 빠른 복사가 가능하지만 매우 큰 배열의 경우 너무 깊게 내려가야 합니다. 연구 결과 32라는 숫자가 트리 깊이를 정할 때 좋은 기준점이 된다고 합니다. 지금까지 말한 것을 전문 용어로 ‘비트맵 벡터 트리’(Bitmapped Vector Trie)’라고 합니다. 때로는 ‘Bit-partitioned Vector Trie’라고도 부릅니다. 지금은 이 용어를 신경 안 쓰셔도 되지만, 궁금하다면 구글에 검색해 보세요.

슬라이드: 가지 수의 많고 적음에 따른 장단점 비교

객체의 키는 어떡하죠?

트라이는 인덱스가 존재하는 배열 같은 경우에 도움이 되는데, 객체는 어떨지 모르겠네요. 단순한 인덱스가 아니라 임의의 키로 객체에 대응하려고 하면 정수가 아닌 값을 키로 사용해야 하는데, 어떻게 하면 될까요? 이제부터는 배열이 아니라 객체 비스름한 데이터 구조를 사용하겠습니다. 원숭이의 ‘M’, 판다의 ‘P’ 같이 단어의 맨 앞글자를 키로 사용할 거에요. 키를 해싱하여 숫자로 만들면 됩니다. 각각의 키는 고유한 숫자가 되겠죠. 해싱하면 키 순서를 유지하지는 않겠지만 괜찮아요. 객체 키는 순서를 유지하지 않아도 됩니다. 해싱 값을 얻은 후에 앞에서 했던 것처럼 이진수로 변환하여 트리를 내려가면 됩니다.

만약에 “F”라는 키에 대응되는 값을 찾아야 한다고 해봅시다. F를 해싱하여 숫자 값을 얻고, 그 값이 5라고 한다면(알파벳 순서를 해싱 함수의 값으로 사용한다 가정하면 ABCDEF 이므로 5), 이진수로 나타내면 101이 되겠죠. 그 후 아까처럼 트리를 내려가면 됩니다. 간단한 설명을 위해 여기서는 이진 트리를 사용할게요. 그렇지만 여기서도 통상적으로는 레벨 당 가지 32개를 사용합니다. 해시 함수를 사용해 임의의 키값을 숫자로 변환했고, 키를 이진수로 표현한 후 이를 따라트리를 내려가서 바라던 동물인 개구리를 얻었어요. 더 자세하게 알아보고 싶다면 구글에서 ‘Hash Array Mapped Trie’를 검색해 보세요. 이 구조의 발전에는 필 백웰(Phil Bagwell)과 리치 히키(Rich Hickey)가 선구적인 역할을 했습니다. 속도 최적화 시도 또한 많이 이루어졌는데, 오늘은 여기에 대해서는 다루지 못하고 기본적인 개념만 이야기합니다. 트리로 데이터를 표현하고 구조적인 공유를 통해 옛날 버전과 새 버전 사이에 공통적인 정보는 최대한 재활용 하는 것이 핵심입니다. 인덱스나 해시 키같이 이진수로 키를 변환한 후, 찾고자 하는 값을 얻기 위해 트리를 내려가는 것 역시 중요한 개념입니다.

정리하자면, 뭔가가 변한다는 것(mutability)는 골치아픈 일입니다. 사이드 이펙트가 없어야 하고 인풋 계산 및 아웃풋 반환을 제외하고는 아무것도 수정하지 말아야 할 FP에서는 이를 피해야 하는 것이 상책입니다. 반대로 불변성은 환영할 만한 것입니다. 불변 데이터를 사용하면 동물만 들어가야 하는 동물원에 외계인을 넣어버리는 등 동료의 프로그램을 엉망으로 만들지 않아도 되기 때문이죠. 하지만 복사에 대해 생각해 봐야 합니다. 자칫하면 효율적이지 않고 시간과 공간을 둘 다 고려하지 않은 방법이 되기 때문입니다. 트리, 혹은 트라이 구조를 사용해 변경 전과 후의 구조 사이에서 최대한 정보를 많이 재활용하는 구조적 공유를 사용하면 복사 성능 문제를 해결할 수 있습니다.

멋진건 알겠는데, 어따 쓰죠? 🤨

이렇게 멋지기 때문에 이 자료 구조와 사랑에 빠질 수도 있는데요, 그러면 어떻게 실전에서 쓰면 될까요? 이거 가지고 이모지 박스를 쌓을 것은 아니잖아요, 그죠? 네 맞아요, 그러지는 않을 겁니다.

Mori와 Immutable.js 비교

불변 자료 구조 JS 라이브러리가 많이 있지만, 오늘은 몇 가지만 소개합니다. 하나는 Mori 입니다. 데이빗 놀란(David Nolan)이 만든 것인데, ClojureScript에 구현된 불변 자료 구조를 JS에서 사용할 수 있게 해줍니다. ClojureScript는 바닐라 JS에 익숙한 분들을 위해 만들어진 Clojure 컴파일러입니다. 함수형 API를 제공하는데, 이 점이 다른 라이브러리와 비교했을 때 두드러지는 모리의 특징입니다. 활용법은 곧 알아볼게요. 다른 한편으로, 페이스북의 리 바이런(Lee Byron)이 만든 Immutable.js가 있습니다. 불변 자료 구조를 JS로 구현한 라이브러리입니다. ClojureScript 보다 네이티브 JS 느낌이 있습니다. 좀 더 객체 지향 스타일의 API를 갖추어서, 사용할 때 꼭 Clojure 배경지식이 없어도 됩니다. 여전히 데이터를 직접적으로 수정하는 대신 데이터를 새로 반환하긴 해도 말이에요.

이제 두 라이브러리를 살짝 볼게요. 모리를 활용해 벡터를 만드는 방법은 아래와 같습니다. 벡터는 모리에서 제공하는 자료 구조 중 하나로 배열과 비슷합니다. 벡터를 만들어서 변수 a에 할당합니다. (a는 배열의 ‘a’rray에서 따옴) 변수 a, a2가 있습니다.

슬라이드: Mori를 활용해 벡터를 만들고 3을 추가하는 예제입니다.

a 벡터에 새로운 요소를 추가하고 싶다면 conj 함수(Clojure 세계에서 온 것입니다)를 사용합니다. 함수에 원본 a를 넣고 여기에 추가하고 싶은 무언가(여기서는 3)를 인자로 줍니다. 그러면 새로운 자료 구조를 만드는 것을 알 수 있죠. 벡터 [1, 2]와 벡터 [1, 2, 3]은 Clojure 벡터이기 때문에, 변환은 가능해도 일반 자바스크립트 배열과는 달라요. 중요한 점은 a2에는 conj 함수에서 반환한 결과를 할당할 수 있고, count 함수를 사용해 확인해 보면 a는 전혀 바뀌지 않았다는 것입니다. 여전히 요소가 2개입니다. 또, get 함수를 사용해 a2에는 요소가 3개 있음을 알 수 있죠.

같은 과정을 Immutable.js로도 수행할 수 있습니다. Imjs.list.of를 사용해 볼게요. 문법이 흥미롭네요. 자바스크립트 배열과 유사한 JS 리스트를 만듭니다. 그다음 a에 새로운 요소를 추가했는데 우리에게 친숙한 점 메소드 표기법(dot-method notation)을 사용했습니다. a.push(3)라고 했어요. 중요한 것은 이렇게 한 후에 a가 변경되지 않는다는 점입니다. push는 그냥 새로운 a를 반환할 뿐이고, 이를 a2에 할당해 보면 a가 바뀌지 않았음을 증명할 수 있습니다. a.size를 치면 2가 나오고, a2의 두 번째 인덱스 요소를 찾아보면 예상한 대로 3이 반환됩니다.

슬라이드: immutable.js를 활용하여 리스트를 만들고 3을 추가하는 예제입니다.

비슷하게 맵(map)이라고 불리는 키-값 객체를 사용해 볼게요. mori의 hashMap 자료 구조를 사용하여 객체 o를 생성합니다. a 키는 값 1을 가지고, b 키는 값 2를 가집니다. 일반 자바스크립트 객체와는 문법이 살짝 다른 것을 다시 볼 수 있습니다. 특별한 불변 자료 구조이므로 특별한 문법을 사용합니다. 값을 변경하고 싶다면 assoc 함수를 사용하면 됩니다. a 키의 값을 3으로 변경하여 새로운 객체를 만들고 변수 o2에 이를 할당합니다. get 함수를 사용하면 원본 o가 바뀌지 않았음을 알 수 있습니다. 예상했던 바와 같이 원본 o 객체의 a 값은 1이고, a2의 a 값은 3이네요. 참으로 불변 자료 구조입니다. 이들은 복잡한 트리 구조를 가지기 때문에 자바스크립트 객체처럼 콘솔에 찍어보면 이상하다고 느낄 수도 있습니다.

슬라이드: mori.assoc을 이용해 a 키값을 3으로 변경한 후 생성된 새로운 객체를 o2에 할당합니다.
슬라이드: immutable.js에서는 o.set을 사용해 키값이 변경된 새로운 객체를 생성합니다.

시험 삼아 둘 다 사용해 보고 어떤 것이 여러분께 맞는지 생각해 보시기를 전적으로 추천해 드립니다. 발표 시간이 다 되기 전에 이 둘을 정말 간략하게 비교해 볼 수 있겠네요. Mori는 Clojure 세계에서 온 라이브러리이고, ClojureScript를 사용합니다. Immutable.js는 o.get()같이 JS에 익숙한 사람이 편하게 사용할 수 있습니다. 그렇지만 이 점이 저에게는 살짝 인지 부조화를 일으킵니다. immutable.js의 API는 마치 호출하면 데이터를 변경하는 것처럼 보이는데(실제로는 그렇지 않지만), 함수형 프로그래밍의 사고방식에 익숙한 저로서는 Mori의 방식이 더 자연스럽습니다. 아웃풋뿐만 아니라 인풋 역시 알아보기 쉽기 때문입니다. 원본 객체를 덮어써서(in-place) 수정한다고 착각할 염려도 없구요. 성능도 사소하게 차이나는데, Mori가 속도는 살짝 빠르지만 라이브러리 크기는 Immutable.js가 좀 더 작습니다. 둘 다 좋은 옵션이니 여러분께 맞는 것을 찾기를 바랍니다.

제 이야기는 여기까지입니다. 아무쪼록 내용이 유익했기를 바라며, 이만 해산할게요. 부디 앞으로 데이터는 변경하지 마세요! 발표 참고 목록은 여기를 보시면 됩니다.

더 알아보기

--

--