프로그래밍 언어의 목적은 무엇이며, 왜 어떤 아이디어는 라이브러리로만으로는 구현할 수 없는지에 대해 Ruby on Rails, Java, C 등의 사례를 통해 설명한다.
HomePhilosophyDownloadsDocumentationPeopleCommunityNewsReference
Patrick S. Li - 2016년 5월 29일
최근에 한 친구가 내게, 모든 프로그래밍 언어는 서로 매우 비슷해 보인다고 말했다. 다 변수도 있고, 배열도 있고, 몇 가지 반복문 구성요소, 함수, 그리고 산술 연산이 있다. 물론 어떤 언어는 일급 함수나 코루틴 같은 더 멋진 기능을 갖고 있지만, 그는 어차피 자신을 전문 프로그래머라고 생각하지도 않고 그런 기능을 쓰지도 않는다고 했다.
그가 말하길, 자신에게 어떤 프로그래밍 언어를 생산적으로 만들어주는 진짜 요소는 그 언어가 함께 제공하는 라이브러리라고 한다. 예를 들어 그는 유명한 Ruby on Rails 웹 프레임워크를 쓰면서 프로그래밍에 입문했다. 그가 혼자서 완전한 데이터베이스 기반 웹 스택을 작성할 방법도 없고, 그럴 의향도 없다. 하지만 Ruby on Rails 덕분에 그럴 필요가 없다! 그래서 그는 Ruby라는 프로그래밍 언어 자체에 대해 특별한 의견은 없지만, Rails는 정말 사랑한다고 말했다. 그 자신처럼 대다수의 프로그래머는 비전문가이고, 비전문가에게 가장 큰 생산성 향상은 사용하기 쉬운 라이브러리의 폭넓은 스펙트럼에서 온다는 것이다. 일급 함수나 객체 시스템 같은 미묘한 언어 기능은 어차피 실제로 쓰지 않으니 그들에게는 와닿지 않는다. 컴퓨터 과학자들은 새 프로그래밍 언어를 발명하기보다 새 라이브러리를 개발하는 데 시간을 써야 한다는 것이다.
프로그래밍 언어에 대한 내 친구의 견해는 흔한 편이고, 나는 전문가와 비전문가 모두로부터 비슷한 말을 반복해서 들어왔다. 물론 나는 언어 설계자이기 때문에 이 의견에 동의하지 않는다. 내가 생각하는 범용 프로그래밍 언어의 목적은 다음과 같다.
우선, 내 친구의 의견은 완전히 옳지만 불완전하다고 말하고 싶다. 가장 큰 생산성 향상은 실제로 폭넓은 라이브러리를 갖는 것에서 나온다. Ruby on Rails는 환상적인 프레임워크이고, 수천(어쩌면 수백만) 명의 비전문가가 정교한 웹사이트를 빠르게 만들 수 있게 해주었다. 그렇다면 자연스러운 질문은 이것이다. 왜 이제는 모든 프로그래밍 언어에 Rails 프레임워크가 있지 않은가?
Ruby와 의미적으로 비슷한 일부 언어들은 자체 웹 프레임워크를 갖고 있다. 예컨대 Python에는 Django가 있다. 하지만 현재까지도 Java에는 Ruby on Rails만큼 사용하기 쉬운 제대로 된 웹 프레임워크가 없다. 왜 그럴까? Java 개발자들이 Ruby 프로그래머보다 능력이 떨어져서일까? David Hansson이 혼자 Rails를 설계하고 개발할 수 있었다면, 왜 어떤 프로그래머 집단이 그 설계를 Java로 그대로 베껴 만들지 못할까? 더 난감한 것은, Java가 애플릿 기술 때문에 처음엔 웹 프로그래밍 언어라고 스스로를 마케팅했다는 사실이다. 이 점을 강조하기 위해 덧붙이면, C에도 좋은 웹 프레임워크는 없고, 앞으로도 나올 가능성이 낮다. C 프로그래머가 Ruby 프로그래머보다 못해서가 아니라는 점을 분명히 해두겠다.
경제성도 이유가 아니다. Tiobe 지수에 따르면 오늘날 가장 널리 쓰이는 프로그래밍 언어는 Java와 C이고, Ruby는 8위다. Ruby 프로그래머보다 Java와 C 프로그래머가 훨씬 많다. 누군가가 Java on Rails를 작성하기만 한다면 그 프레임워크는 Ruby on Rails보다 몇 배 더 많은 사용자를 얻을 것이고, 즉시 인터넷의 명성과 부를 얻게 될 것이다.
즉 무능 때문도 아니고 경제성 때문도 아니다. 그럼 왜 누군가 Ruby on Rails를 Java로 포팅하지 못할까? 결론부터 말하면, 그들은 할 수 없기 때문이다.
Ruby를 잘 아는 프로그래머가 Rails 입문 튜토리얼을 깊이 들여다보면, Ruby 언어 기능이 거의 전부 어떤 방식으로든 쓰이고 있음을 알아차릴 것이다. Rails의 ActiveRecords 라이브러리는 Ruby의 메타프로그래밍 기능을 광범위하게 사용한다. Rails의 템플릿 시스템은 Ruby의 런타임 평가 기능에 크게 의존한다. 웹사이트가 사용자 클릭에 반응하도록 만들기 위해 ApplicationController를 서브클래싱하고, 다양한 믹스인을 임포트하여 미리 코딩된 기능을 재사용한다. 이벤트는 종종 어떤 위젯에 일급 함수 형태의 콜백을 붙이는 방식으로 처리된다. Ruby는 동적 타입이며 가비지 컬렉션을 제공하므로, 캐주얼한 웹사이트 제작자는 타입과 메모리 해제라는 개념을 안전하게 완전히 무시할 수 있다. 이런 기능들은 다른 모든 언어에 존재하지 않는다. 예를 들어 Java의 메타프로그래밍 기능은 ActiveRecords 같은 시스템을 구현하기에 충분히 강력하지 않다. Rails는 Ruby이기 때문에 가능한 것이다.
따라서 내 친구는 자신이 관심 없다고 했던 그 미묘한 언어 기능들을, 실제로는 전혀 모르는 사이에 아주 많이 사용하고 있는 셈이다. 그리고 이는 의도된 바다! Ruby on Rails는 타입 이론이나 메모리 관리, 객체지향 디자인 패턴을 이해하지 않아도 웹사이트를 만들 수 있게 하도록 설계되었다. Rails는 웹사이트 제작자가 소프트웨어 인프라를 관리하는 대신 웹사이트 설계에 집중할 수 있게 해준다. 내 친구는 Ruby의 모든 이점을 누리면서도 그것을 자각하지 못하고 있는데, 바로 그게 핵심이다.
한 걸음 물러나 보면, 코드를 사용하기 쉬운 라이브러리로 패키징하는 개념은 새롭지 않다. 프로그램이 천공 종이테이프에 저장되던 시절에도 존재했다. 지금도 유용한 서브루틴을 담은 방대한 어셈블리 라이브러리들이 있다. 그리고 지금까지 설계된 모든 프로그래밍 언어는 공통 기능을 재사용할 수 있는 어떤 방식이든 제공해 왔다. 내게 범용 프로그래밍 언어의 1차적 목적은, 사용하기 쉬운 라이브러리의 폭넓은 스펙트럼을 만들 수 있게 하는 데 있다.
프로그래밍 언어의 설계는, 어떤 종류의 라이브러리를 쓸 수 있는지와 그것들이 최종적으로 얼마나 사용하기 쉬운지를 직접 결정한다. C 언어에서 재사용을 가능하게 하는 유일한 주요 기능은 함수를 선언하고 호출하는 능력이다. 그래서 어떻게 되느냐? 대부분의 C 라이브러리는 본질적으로 함수들의 큰 모음이다. Ruby on Rails는 “버튼이 클릭되면 이것을 하라”를 간결하게 표현할 수 있다. 여기서 “이것을 하라” 부분은 Ruby에서는 일급 함수로 구현된다. Java처럼 일급 함수를 지원하지 않는 언어에서는 어떻게 구현할까? 일급 함수의 동작은 perform_action 메서드 하나만 가진 새로운 이벤트 핸들러 클래스를 정의하고, 그 클래스의 인스턴스를 버튼 객체에 전달하는 식으로 흉내 낼 수 있다. 그래서 어떻게 되느냐? Java 라이브러리를 쓰려면 보통 엄청나게 많은 핸들러 클래스를 선언해야 한다. 프로그래밍 언어는 그 언어의 라이브러리 설계를 직접적으로 형성한다.
소프트웨어 초기에는 함수 모음만으로도 재사용 가능한 컴포넌트를 작성하는 데 충분했다. 초기 소프트웨어의 상당수는 수치 계산 중심이었고, 실행하고 싶은 모든 수치 알고리즘마다 라이브러리 함수가 하나씩 있었다. 숫자가 들어가고 숫자가 나온다. 이런 경우 함수면 완벽히 충분했다. Unix와 C 역시 대부분의 컴퓨팅이 배치 모드로 이뤄지던 시기에 설계되었다. 입력 데이터를 준비하고 함수를 호출하거나 프로그램을 실행하면 출력 데이터가 돌아온다. 하지만 컴퓨팅은 70년대 이후 급격히 변했다. 오늘날 흥미로운 프로그램 대부분은 대화형이다. 사용자가 버튼을 클릭하면 무언가가 일어나야 한다. 70년대의 라이브러리 기능을 확장하고 싶어 하는 일은 드물었다. 라이브러리는 유용한 함수들의 모음을 제공한다. 그중 하나가 원하는 일을 하면 쓰고, 아니면 직접 작성하면 된다. 그러나 대화형 소프트웨어의 출현과 함께 확장 가능한 라이브러리의 필요성이 분명해졌다. 프로그래머들은 GUI 라이브러리가 “사용자가 버튼을 클릭하면 내 코드를 실행해 달라”라고 말할 수 있게 해주길 원했다. Java(와 C++)는 서브클래싱 메커니즘을 통해 기존 라이브러리의 기능을 확장할 수 있는 제한적인 방법을 제공한다. 그래서 Java 라이브러리를 사용하는 일은 종종 여러 ‘마법 같은’ 클래스를 서브클래싱하고, 여러 ‘마법 같은’ 메서드를 오버라이드하는 것으로 구성된다. 이런 스타일의 라이브러리가 한때 너무 만연해져서, 우리는 아예 새로운 이름까지 붙였다. 그것들이 바로 프레임워크다.
아마도 많은 범용 프로그래밍 언어는, 저자가 당시 사용하던 언어로는 좋은 라이브러리를 작성할 수 없었던 탓에 처음 설계되었을 것이라고 나는 추측한다. 예를 들어 Stanza를 설계해야겠다고 생각하게 된 초기 계기는, Java로 사용하기 쉬운 게임 프로그래밍 라이브러리를 작성하려다 겪은 좌절에서 나왔다. 동시성을 다루기 위해 전통적인 게임 프로그래밍 프레임워크들은 스프라이트의 행동을 상태 기계(state machine) 모델로 프로그래밍하도록 요구했다. 하지만 우리는 머릿속에서 스프라이트를 그렇게 직관적으로 생각하지 않는다. 직관적으로는 캐릭터의 행동을 일련의 단계로 생각한다. 예를 들어, 먼저 캐릭터가 점프하고, 착지한 뒤 왼쪽과 오른쪽을 살펴 가장 가까운 적을 찾는다. 적을 보면 공격하러 가고, 아니면 다시 점프한다. 이를 세 번 반복하고, 세 번 점프해도 적이 보이지 않으면 잠깐 낮잠을 잔다. 이런 단계의 연속을 상태 기계로 변환하는 과정은 믿기지 않을 만큼 지루하고 오류가 나기 쉽고, 무엇보다 반복적으로 느껴진다. 마치 같은 일을 계속하는 느낌이었다. 그래서 자연스러운 질문은 이것이다. 이 상태 기계 변환을 라이브러리로 만들어 재사용할 수 없을까? 적어도 Java에서는 불가능하다는 결론이 났다. 내가 필요로 했던 언어 기능은 어떤 종류의 코루틴 또는 컨티뉴에이션 메커니즘이었다. 조사 끝에 Scheme 언어가 컨티뉴에이션을 지원한다는 것을 알게 되었고, 그 결과 Scheme 버전의 게임 프로그래밍 라이브러리는 Java 버전보다 훨씬 사용하기 쉬웠다.
컨티뉴에이션 지원 덕분에 Scheme 버전의 게임 라이브러리는 사용자가 스프라이트 행동을 상태 기계로 작성할 필요가 없다. 하지만 모든 면에서 Java 버전보다 나았던 것은 아니다. 가장 중요한 차이는 Java 버전이 정적 타입 언어였기 때문에 컴파일러가 많은 실수를 자동으로 잡아줬다는 점이다. Scheme 버전에는 그런 능력이 없어서 게임을 디버깅하는 데 시간이 더 걸렸다. 이 지점에서 던져야 할 올바른 질문은, “그럼 Scheme에 정적 타이핑 라이브러리를 작성해서 타입 오류를 자동으로 검사하게 만들 수는 없나?”일 것이다. 그리고 현재의 답은, 당분간 그리고 예측 가능한 미래에도 ‘아니오’다. 오늘날 어떤 주류 언어도 라이브러리로 타입 시스템을 확장할 수 있게 해주지 않는다. Stanza도 마찬가지다. 다만 더 넓은 대상을 위해 유용한 타입 시스템을 제공하려고 시도할 뿐이다.
범용 프로그래밍 언어의 목적이 강력한 라이브러리 생성을 가능하게 하는 것이라면, 서로 다른 언어들은 “라이브러리로는 작성할 수 없는 기능을 무엇으로 제공하는가”로도 특징지을 수 있다. Stanza는 선택적 타입 시스템, 가비지 컬렉션, 멀티메서드 기반 객체 시스템을 제공한다. 하지만 Stanza의 객체 시스템이 마음에 들지 않는다고 해서, 여러분이 직접 객체 시스템을 작성할 방법은 없다. 이것은 프로그래밍 언어 연구의 주요 방향 중 하나다. 라이브러리 작성자가 자신의 애플리케이션에 맞는 가장 적절한 객체 시스템이나 타입 시스템을 쉽게 작성할 수 있을 만큼 표현력이 높은 언어를 설계할 수 있을까? 언젠가 그런 언어가 나올지도 모른다. Racket과 Shen은 타입 시스템을 확장하는 메커니즘을 제공하며, 메타-객체 프로토콜(meta-object protocols)에 대한 연구는 확장 가능한 객체 시스템을 설계하려는 시도였다. 따라서 언어들은 그 안에서 어떤 종류의 라이브러리를 작성할 수 있는지, 그리고 어떤 종류의 라이브러리를 작성할 수 없는지에 의해 구별된다.
요약하면, 범용 프로그래밍 언어의 목적은 강력하면서도 사용하기 쉬운 라이브러리를 만들 수 있게 하는 데 있다. 언어가 강력할수록 라이브러리는 더 사용하기 쉬워진다. 잘 다듬어진 라이브러리를 사용하는 코드는 마치 동료에게 주는 지시문처럼 읽혀야 한다. 그러니 다음에 특히 우아한 라이브러리를 마주한다면, 그것이 가능하도록 만들어 준 수십 년간의 언어 연구가 있었다는 사실을 떠올려라. 어떤 언어 기능이 그 라이브러리에서 구체적으로 사용되는지 궁금하다면 더 깊이 파고들어 탐구하며, 그 구현에 담긴 고민을 감상할 수 있을 것이다. 이런 미묘한 언어 이야기들이 궁금하지 않다면, 그것들을 모두 안전하게 무시하고 자신의 일을 계속하면 된다. 그게 바로 핵심이다.
사이트 디자인: Luca Li. Copyright 2015.