개인적으로 임작가님 책을 좋아해서 그렇기도 하지만, 프로그래밍 언어가 어떠한 방향으로 진화하고 있는지, 앞으로 우리는 어떠한 자세로 프로그래밍 언어를 학습해야 하는지 등 프로그래밍 언어 학습에 관심이 있다면 이 책을 사서 읽어보기를 추천합니다(재밌어요). 이 책은 크게 자바, C#, 스칼라 챕터로 나누어져 있는데 각 언어가 어떻게 탄생하게 되었고, 어떠한 방향으로 진화했는지, 그리고 앞으로는 어떠한 방향으로 진화할 것인지에 대해서 설명하고 있습니다. 이 책이 쓰여진 시점에는 자바7이 최신 버전이었기 때문에 책에서 설명하고 있는 자바는 지금의 자바와 다를 수 있습니다.

polyglot0

폴리글랏 프로그래밍 : 새로운 자바 언어를 기다리는 히치하이커를 위한 안내서 - 임백준 지음

에필로그

우리는 지금 폴리글랏 프로그래밍의 세계로 진입하고 있다. 하나의 언어가 머리에서 발끝까지, GUI 프론트엔드에서 서버 깊숙한 곳에 있는 백엔드 시스템에 이르기까지 단일하게 사용되는 경우는 찾아보기 어려운, 희귀한 일이 되었다. 한 사람이 여러 명의 배우자와 함께 살아가는 것을 영어로 폴리가미Polygamy 라고 말한다. 이와 비슷하게 여러 개의 언어를 사용하는 것을 폴리글랏Polyglot 이라고 말한다.

저명한 프로그래머들은 적어도 한 해에 하나의 언어를 학습해 나갈 것을 권장하고 있다. 이런 사람들이 주장하는 바에 의하면 앞으로는 프로그래머가 어떤 언어에 대해서 얼마나 많이 알고 있는가 하는 것이 아니라 어떤 언어를 얼마나 빠르게 학습할 수 있는가하는 것이 더 중요하다.


자바

닐 게프터

닐 게프터(Neal Gafter)는 자바 진영을 대표하는 리더 중 한 명이었다. 그가 조슈아 블로흐와 함께 '자바 퍼즐러(Java Puzzlers)'를 쓰고 다양한 컨퍼런스에 강연을 하러 다니던 시절은 아마도 그의 전성기였을 것이다. 게프터는 한 인터뷰에서 프로그래밍 언어를 크게 두 개의 범주로 나누어서 바라보자고 제안했다. 하나는 '살아 있는 언어'고, 다른 하나는 '레거시legacy' 언어다. 새로운 프로젝트를 시작할 때 우리는 살아 있는 언어를 고려하고, 이미 존재하는 코드베이스를 유지 보수할 때는 레거시 언어를 사용한다. 게프터에 의하면 언어를 이와 같은 방식으로 구분하는 것은 특정한 언어에 새로운 기능을 더할지 말지를 결정할 때 도움을 준다. 어떤 언어가 이미 레거시 언어라면 그 언어에 대한 변경은 이미 존재하는 코드를 수정하는 데 도움을 주는 수준으로 제한하는 것이 옳다. 하지만 만약 그것이 발전을 거듭하고 있는 살아 있는 언어라면 언어에 대한 변경이 새로운 시스템의 설계나 개발에 도움을 주는 방향으로 확장될 필요가 있다. 다시 말해서 살아 있는 언어는 비본질적인 복잡성accidental complexity 을 감소시키는 방향으로 계속 진화해야 한다는 것이다.

"상위수준의 언어가 성취하는 것은 무엇인가? 그것은 비본질적인 복잡성으로부터 프로그램을 자유롭게 만드는 것이다. 추상적인 프로그램은 개념적인 구조물로 이루어진다. 그러한 구조물은 연산자, 데이터 타입, 열, 그리고 통신 등이다. 구체적인 기계어 프로그램은 비트, 레지스터, 조건, 분기문, 채널, 디스크와 같은 대상을 염두에 두어야 한다. 상위수준의 프로그래밍 언어는 이와 같은 구조물들을 추상적인 프로그램 안에서 완전히 사라지도록 만들어주는 것이다." - 프레드 브룩스(Fred Brooks)

쉽게 말하면 프로그래머는 시시콜콜하고 세세한 대상을 반복해서 다루는 대신, 더 크고 개략적인 추상적인 개념을 가지고 주어진 문제를 해결하는 데 집중해야 한다는 말이다. 우리가 웹브라우저에서 돌아가는 GUI를 만들려고 하는데 그 전에 직접 HTTP 프로토콜을, 혹은 심지어 TCP/IP 스택을 구현해야 한다면 GUI가 제공하는 비즈니스 콘텐츠에 신경을 쓸 여력이 없을 것이다. 이러한 맥락에서 보면 HTTP 프로토콜과 TCP/IP는 비본질적인 복잡성에 해당한다. 게프터는 비본질적인 복잡성이 강력한 '추상'의 힘에 의해서 사람들의 시야에서 성공적으로 사라진 예로 가비지 컬랙션을 언급한다.

제네릭

자바 제네릭의 기초를 설계한 마틴 오더스키는 이렇게 말했다.

"자바 제네릭을 디자인할 때 우리는 너무나 많은 제약 때문에 힘들었습니다. 그중에서도 가장 다루기 힘든 제약은 새로운 코드가 과거에 제네릭을 사용하지 않고 작성된 코드도 완전히 호환이 되도록 만들어야 한다는 것이었습니다. 문제는 그때가 썬이 자바 1.2와 함께 출시한 컬랙션 라이브러리를 완성한 지 얼마 되지 않았을 때였기 때문에 제네릭을 더한다고 해서 컬랙션 라이브러리를 또 다시 작성할 준비가 되어 있지는 않았다는 것입니다. 그래서 우리는 기존 코드의 입장에서 보았을 때 제네릭이 완전히 투명하게 보이도록 만들어야만 했습니다."

"자바 제네릭이 지저분한 부분을 많이 가지고 있는 이유는 바로 그것 때문입니다. 제네릭화된 타입이 있으면 반드시 소위 원시raw 타입라고 불리는 제네릭화되지 않은 타입도 함께 존재해야만 했습니다. 배열의 동작과 기능에도 전혀 손을 댈 수 없었기 때문에 그저 컴파일러가 검사되지 않은 경고를 화면에 나타내도록 하는 정도밖에 할 수 없었습니다. 배열에 손을 댈 수 없었기 떄문에 우리는 자바 내부에서 어떤 타입 T가 있다고 했을 때 그러한 타입 T를 저장하기 위한 배열을 생성한다거나 하는 식으로 배열을 이용한 일을 아무것도 할 수 없었습니다. 우리는 나중에 스칼라에서 그렇게 할 수 있는 방법을 알아냈는데, 그것은 스칼라 안에서 배열이 공변covariant 이어야 한다는 요구사항을 구현했기 때문입니다."

자바에서 제네릭과 관련된 정보는 소스코드 차원에서만 존재하며, 컴파일이 되어 바이트코드가 되고 나면 제네릭과 관련된 기억이 남김없이 사라진다. 즉, 클래스캐스트를 이용하던 자바 5 이전의 코드와 다를 바 없게 된다. 그렇기 때문에 자바에서는 제네릭의 사용으로 인한 실행시간 성능 향상은 없으며, 제네릭을 이용한 코딩을 할 때 실행시간 타입에 접근할 방법이 없어서 기능이 제한된다. 이런 의미에서 자바에서 구현한 제네릭은 아주 표면적인 컴파일러 트릭, 혹은 문법적 당의정(sugarcoating)에 불과하다. 바이트코드와 JVM을 뜯어서 고치지 않는 이상 이러한 지우개 기능은 자바와 영원히 운명을 함께 할 것이다.


C#

속성과 대리자

C# 1.0은 실제로 자바와 다를 것이 별로 없었다. 자바의 창시자인 제임스 고슬링과 썬마이크로시스템즈의 공동창업자 빌 조이는 C#이 '자바의 모방'이라고 말했다. 하지만 C# 1.0은 자바와 다른 점도 가지고 있다. 대부분 사소한 문법적 차이처럼 보이는 것이긴 하지만 그들은 훗날 C#과 자바가 상당한 수준으로 달라지도록 하는 데 중요한 기초를 제공했다. 짚고 넘어갈 필요가 있는 중요한 차이로는 속성property 과 대리자delegate 가 있다.

C# 2.0은 익명 메서드anonymous methods 라는 문법적 트릭을 통해서 프로그래머가 대리자를 한층 더 편리하게 사용할 수 있게 만들었고, 2007년에 출시된 C# 3.0에는 마침내 람다를 포함시켰다. 직접적으로는 잘 사용되지 않는 대리자를 잘 이해해야 하는 이유는 대리자가 바로 C#이라는 객체지향 언어에서 람다라는 함수적 표현이 가능하게 만들어준 기능이기 때문이다. 람다표현이 다른 메서드에게 인수로 전달되거나 반환값으로 되돌려질 때, 그리고 람다라는 '익명의 함수'가 실행환경 속에서 마음껏 떠돌아다닐 수 있는 것은 대리자 타입을 가진 '객체'가 가상머신 내부에서 람다표현이라는 '익명 함수'를 대신해서 생성하기 때문이다. 대리자는 람다라는 천상의 개념에게 뼈와 살을 내어주는 지상의 육신이다.

일급함수

알란 튜링의 대학 동료였던 크리스토퍼 스트라취(Christopher Strachey)는 취미로 컴퓨터 프로그래밍을 하였는데, 그는 컴퓨터 역사상 최초로 음악을 연주하는 프로그램을 만들기도 했다. 그 프로그램은 우리가 '트윙클 트윙클 리틀 스타' 혹은 '반짝 반짝 작은별'이라는 노래로 많이 부르는 그 노래의 멜로디를 연주했다. 스트라취는 컴퓨터 세계에 커다한 영향을 미치는 두 가지 업적을 남겼는데, 하나는 시분할time-sharing 이고, 다른 하나는 '일급함수first-class function' 라는 표현이다.

'일급 함수'라는 표현은 어떤 프로그래밍 언어가 '함수'를 자유롭게 다른 함수에게 인수로 전달하고, 함수 자체를 반환값으로 사용하고, 변수에 함수를 할당할 수 있고, 데이터구조에 함수를 저장할 수 있도록 저장한다는 뜻이다. 객체지향 프로그래밍 언어에서는 '일급객체'가 성립하는 것처럼, 함수형 프로그래밍 언어에서는 '일급함수'라는 표현이 성립한다. 어떤 함수에게 함수를 인수로 전달한다고 했을 때, 다른 함수를 인수로 받아들이는 함수를 고계함수higher-order function 라고 부른다.

1936년 알론조 처치(Alonzo Church)는 '람다계산법(Lambda Calculus)'이라는 것을 이용해서 알고리즘으로 결정할 수 없는 문제가 존재함을 보였고, 직후에 알란 튜링(Alan Turing)은 '튜링 머신(Turing machine)'을 고안해서 알고리즘으로 수행하는 기계적 장치를 이용해서 해결할 수 없는 문제가 존재한다는 사실을 보여주었다.

여기서 한 가지 기억해 둘만한 것은 처치의 람다표현에서 '함수'는 '값'과 언제나 등가물이라는 사실이다. 다시 말해서 함수와 값은 언제나 서로 대신하여 사용될 수 있다. 함수는 값이고 값은 함수다. 함수형 프로그래밍 패러다임의 핵심은 바로 이 명제, 함수는 값이고 값은 함수라는 명제에 놓여 있다. 이 명제가 중요한 이유는 함수와 값의 등가성이 유지되기 위해서는 함수가 반드시 어떤 값을 반환해야 하며, 값을 반환하는 것 이외에는 내부에서 어떤 종류의 부수효과side-effect 도 갖지 말아야 함을 뜻하기 때문이다. 만약 함수가 부수효과를 갖는다면 함수와 값의 등가성은 파괴된다.

크리스토퍼 스트라취는 '일급함수' 이외에 '커링currying' 이라는 용어도 만들어냈다. 커링이라는 개념을 스스로 개발한 것은 아니지만 함수형 프로그래밍에서 중요하게 사용되는 테크닉에 하스켈 커리의 이름을 따서 커링이라는 멋진 이름을 붙인 것이다. 스트라취가 남긴 족적을 따라가 보면 그가 함수형 프로그래밍 패러다임의 정립에 상당히 많은 기여를 했음을 확인하게 된다.

링큐 LINQ

링큐는 C# 언어에 존재하는 다양한 기능들이 결합하면서 탄생한 C# 언어의 꽃이다. 이 꽃은 함수형 프로그래밍 패러다임이라는 거대한 파도에 자신의 서핑보드를 얹기를 희망하는 프로그래밍 고수들에게 매혹적인 향기를 풍기며 그들을 유혹했다.

polyglot2

C#의 창시자, 앤더스 하일스버그

링큐를 만드는 데 사용된 C#의 기능으로는 우선 제네릭이 있었고, 람다, 타입유추type inference, 확장메서드extension methods, 암묵적 타입 변수implicitly typed variable, 객체 초기자object initializer, 익명 타입anonymous types 등이 존재했다. 이러한 기능 중에서 당시 자바7에 존재하는 기능은 제네릭이 유일하다.

자바와 C#은 한때 쌍둥이처럼 닮은 언어였지만, 10년의 시간이 지나는 동안 서로 다른 길을 걸으면서 다른 언어가 되었다. 그 다른 길이란 바로 대리자였고, 람다였고, 링큐였다. C#은 비록 마이크로소프트의 가상머신 안에 갇히는 운명을 타고 났지만, 언어 자체만 놓고 보면 아름답고 유연하다. 특히 자바에 비해서 상대적으로 우아하고 간결한 표현이 가능하다.

그래서 자바를 사용하는 프로그래머들은 자신의 프로젝트에서 C#이 사용되는가 여부와 무관하게 C# 언어를 학습할 필요가 있다. C#은 자바 8을 포함하여 앞을 자바가 걸어갈 수 있는 미래의 모습을 이미 갖추고 있는 자바의 미래이기 때문이다.


스칼라

마틴 오더스키

"적어도 언어의 한 부분은 언어의 성장을 도울 수 있는 방식으로 설계되어야 한다. 새로운 타입의 집합, 사용자가 정의한 새로운 타입, 새로운 어휘와 규칙을 언어에 더하고, 온갖 종류의 패턴을 활용하는 것이 모두 가능해야 한다. 언어를 설계하는 사람이 언어에 포함되는 스무 가지 종류의 숫자 타입을 일일이 정의해서는 곤란하다. 물론 스무 가지 종류의 숫자 타입을 필요로 하는 사용자는 어딘가에 분명히 존재할 것이다. 그렇다고 한다면 사용자 자신이 언어에 그러한 타입을 직접 도입하고, 플러스나 아니면 그와 비슷한 다른 연산자 혹은 비트 연산 등이 모두 제대로 동작하도록 보장할 수 있는 방법이 필요하다." - 가이 스틸(Guy L. Steele Jr)

스칼라 2.8의 스펙을 담은 PDF 파일이 190 페이지 정도에 불과한 데 비해서 오라클의 웹사이트에서 내려받을 수 있는 자바 스펙의 PDF 파일은 무려 700 페이지가 넘는 방대한 분량이다. 마이크로소프트에서 내려받을 수 있는 C# 언어의 스펙을 담고 있는 워드 파일은 500 페이지가 넘는다. 수행할 수 있는 기능의 폭과 깊이가 거의 비슷한 자바, C#과 스칼라 사이에 이렇게 커다란 차이가 존재하는 이유(자바와 C#이 더 오랜 역사를 가지고 있긴 하지만)는 스칼라 언어에는 창시자인 오더스키의 철학이 반영되어 있기 때문이다. 언어 자체의 규칙이 작은 크기를 가지고 있어도, 언어의 규칙 자체가 가이 스틸이 말하는 것처럼 언어의 성장을 도울 수 있는 방식으로 설계되어 있다면 다른 언어에 비해서 기능이 부족할 이유가 없다. 문제는 언어의 규칙을 담은 파일의 크기가 아니고, 규칙의 확장성이다.

확장이 가능한 언어 scalable language 는 시간이 지남에 따라서 확장성이 부족한 언어보다 오히려 더 풍부한 기능을 갖추게 된다. 확장이 가능한 언어. Scalable Language. 스칼라라는 언어는 이렇게 적은 규칙과 (스스로의 확장을 통한) 풍부한 기능이라는 마틴 오더스키의 철학을 구현하기 위해서 세상에 탄생했다.

polyglot1

왼쪽부터 마틴 오더스키, 에릭 마이어, 로랄드 쿤

언어의 추상

추상abstraction 은 각각 다른 영역에 존재하는 디테일을 서로 다른 수준에서 다룸으로써 복잡성을 낮추는 것을 의미한다. 어떤 집합적 개체aggregate entity 를 대상으로 작업을 하고 있다면 추상을 대상으로 작업을 하고 있는 것이다. 어떤 대상을 유리와, 나무와, 못의 집합으로 묘사하는 대신 간단히 '집'이라고 지칭한다면, 그 순간 일종의 추상을 만들어낸 것이다. 집들이 여러 개 모여 있는 것을 '마을'이라고 지칭한다면, 그 순간 또 다른 수준의 추상을 만들어 낸 것이다. 추상은 '계층hierarchy' 보다 일반적인 개념이다. 예를 들어서 추상은 엄격한 구조를 갖는 계층과 달리 서로 느슨한 네트워크를 이루고 있는 컴포넌트들에게 디테일을 분산시킴으로써 복잡성을 감소시킬 수 있다. - 스티브 맥코넬(Steve McConnell)

맥코넬이 이야기하는 추상이 여러 개체를 아우르는 집합에 이름을 부여하는 방식의 추상을 의미한다면, 그와 다른 방식의 추상도 존재한다. 구체적인 존재의 한 단면을 의식적으로 골라내서 추상화하는 방법도 있고, 구체적인 존재가 품고 있는 복잡성을 시야에서 감추어서 단순화시킴으로써 추상하는 방법도 있다. 편의상 앞의 것을 '껍데기 추상'이라고 부르고, 뒤의 것을 '커튼 추상'이라고 부르자. 커튼추상이라는 말은 '커튼으로 가리기 추상'을 줄인 표현이다. 복잡한 것들을 커튼으로 가려서 단순하게 보이도록 만든다는 의미다.

예를 들어, 박지성이나 손흥민 같은 사람이 있다면 우리가 그들을 가리켜서 "축구 좀 하는 애"라고 말한다면 그 표현은 박지성과 손흥민이라는 구체적인 존재가 가지고 있는 어느 한 단면을 도려내어서 추상하는 것이다. 이건 개체의 집합을 묶어서 이름을 부여하는 추상과 성격이 다르다. 이와 같은 성격의 껍데기추상은 대개 프로그래밍 언어의 표현expression 이 압축적이고 간결해지는 과정에서 많이 발견되고, 이는 객체지향 방법론의 핵심이다.

한편 복잡성을 시야에서 사라지게 만들어서 우리가 바라보는 대상을 간결하게 만드는 방식의 커튼추상도 존재한다. 프로그래밍 언어가 문법적 당의정을 활용해서 똑같은 표현을 더 간결하게 만들 수 있게 하거나, 언어 자체의 문법이 아니라 라이브러리나 프레임워크를 이용해서 반복된 표현을 하지 않도록 만드는 것 등이 이러한 추상의 예다.

트레이트

새로운 프로그래밍 언어에 포함되어 있는 낯선 개념을 접할 때나 자기가 사용하는 언어에 새로운 기능이 추가될 때, 그런 개념이나 기능을 문법적인 측면으로만 접근하는 것은 좋지 않다. 그렇게 하면 그들이 '어떤 문제'를 해결하기 위해서 고안되었는지, 그 문제를 해결하는 데 '왜' 도움이 되는지 이해하지 못한 상태에서 단순히 API를 암기하는 데 초점을 맞추게 되기 때문이다. 예를 들어, 스칼라의 trait은 자바와 C#이 가지고 있는 인터페이스라는 기능의 한계를 해결하기 위해 고안되었다. 자바나 C#의 인터페이스가 갖는 문제는 어떤 클래스가 그것을 구현하는 순간 그것이 정의하고 있는 API 전체를 받아들여야 한다. 존재 자체를 받아들여야 하는 것이다. 반면에 어떤 클래스에게 어떤 특정한 특질을 주입하는 믹스인mix-in 을 사용하면 클래스가 가지는 기능을 깔끔하게 정의할 수 있다.

자바나 C#에서 사용하는 인터페이스라는 구조물이 타입안전성type safe 를 보장하기 위해서 사용되는 정적 타입시스템과 상당히 관련이 깊은 개념이라고 한다면, 그에 비해서 믹신은 타입과 관련된 규칙이 매우 자유분방하고 유연한 동적 프로그래밍 언어에서 애용되는 구조물이라고 할 수 있다. 마틴 오더스키는 스칼라를 설계하면서 자바의 인터페이스가 가지고 있는 한계를 극복하면서 그와 동시에 C++의 복수상속이 야기하는 문제점을 피하기 위해서 믹신과 거의 다를 바가 없는 개념인 트레이트라는 타입을 도입했다.

// 하나의 트레이트 안에 넣는 대신 특정 특질만 가지고 있도록 별도의 트레이트로 쪼갬.
trait Sleeper
trait Eater
trait Worker

// 특질을 필요에 따라서 원하는 클래스에 주입
class SmartPhone extends Phone with Eater
class SamsungPhone extends SmartPhone with SBeam with ShareShot
class IPhone extends SmartPhone with VideoPlayer with Siri

에필로그

이 책을 읽고 '자바는 죽었고 이제부터 대세는 스칼라'라고 주장하는 것으로 생각할지 모른다. 폴리글랏 프로그래밍이라는 개념을 이용해서 강조하고자 하는 핵심 메시지는 그와 같은 이분법적인 사고 자체가 잘못되었다는 점을 밝히는 것이다. A가 아니면 B라는 식의 흑백논리는 시대에 대한 역행이다. 대세 따위는 없다. JVM 위에서 여러 개의 언어가 백가장명하는 시기는 생각보다 오래 지속될 것이다. 예컨대 자바는 스칼라나 클로저 같은 언어들이 가지고 있는 기능을 흉내 내면서 생명을 연장하려고 할 것이고, 다른 언어들은 라이브러리, 개발도구, 커뮤니티의 활성화를 통해서 자바의 기반을 잠식하려고 노력할 것이다.

앞으로 프로그래머는 어느 하나의 언어에 안주할 수 없다. 패러다임을 달리 하는 여러 개의 언어를 자유롭게 구사하지 않으면 살아남을 수 없는 폴리글랏 프로그래밍의 시대가 되었기 때문이다. 자신의 포트폴리오를 어떤 언어로 구성하는지는 각자의 몫이다. 하지만 앞으로는 프로그래머가 어떤 언어에 대해서 얼마나 많이 알고 있는가 하는 것이 아니라 어떤 언어를 얼마나 빠르게 학습할 수 있는가 하는 것이 더 중요하다는 점을 기억하기 바란다.


Reference