주요 리스프 방언인 Common Lisp, Clojure, Racket, Elisp의 차이점과 장단점, 학습 자료, 어떤 상황에서 무엇을 선택하면 좋은지 정리한 안내서.
대부분의 프로그래밍 언어는 하나의 단일 언어로 진화합니다. Python, Java, Javascript, C++는 새 버전과 표준, 여러 구현체를 가지지만, 여전히 같은 언어로 남아 있습니다. C와 C++는 GCC나 Clang으로 컴파일할 수 있고, Python은 CPython이나 PyPy로 컴파일할 수 있으며, 같은 JavaScript는 Firefox와 Chrome 모두에서 실행되고, Java 프로그램은 JVM이나 GraalVM에서 실행될 수 있습니다.
리스프는 그렇지 않습니다.
리스프 로고, NASA 버전.
Wikipedia 페이지에는 20개가 넘는 서로 다른 방언(dialects) 이 나열되어 있으며, 이는 선택할 수 있는 변형이 많다는 뜻입니다. 그 이유는 리스프가 프로그래밍 언어의 가족 이기 때문입니다. 이들은 같은 기본 구문을 공유하지만, 연산자, 의미론, 표준 라이브러리, 언어 기능에서는 서로 다릅니다.
리스프 입문자들이 가장 많이 걱정하는 것 중 하나는 처음에 어떤 방언을 배워야 하느냐는 점입니다. 저는 이 질문이 온라인 포럼에서 자주 올라오는 것을 봅니다. 대답은 방언이 중요하긴 하지만, 초보자가 생각하는 것만큼 크지는 않다는 것입니다. 리스프를 배운다는 것은 새로운 종류의 프로그래밍을 배운다는 뜻입니다. 코드로 문제를 생각하는 새로운 방식입니다. 어떤 방언으로 시작하든 근본 개념은 배울 수 있습니다. 그리고 하나를 익히고 나면, 다른 방언으로 옮겨 가는 것도 비교적 쉽습니다.
저는 현재 활발히 사용되고 유지보수되는 가장 중요한 방언들을 간단히 소개하겠습니다. 여러분이 망설임을 줄이고 리스프 여정을 시작할 하나를 고를 수 있도록, 각자의 강점과 약점을 강조해 보겠습니다.
초보자라면 이 글에 나오는 많은 개념이 완전히 새로울 수 있습니다. 아직은 너무 걱정하지 마세요. 저도 초보자였을 때 이런 개념들 중 많은 것들이 무척 흥미로웠고, 그것이 리스프 세계를 더 배우고 싶게 만들었습니다.
줄여서 CL이라고 하며, 모든 리스프 방언 가운데 가장 성숙하고 포괄적입니다. 이 언어는 1994년에 공식 ANSI 명세로 표준화되었기 때문에 고전적인 리스프로 여겨집니다. 이제부터는 이를 언어 라고 부르겠습니다. 이 표준화 덕분에 서로 다른 플랫폼과 사용 목적을 겨냥한 여러 Common Lisp 구현체가 존재합니다.
가장 유명한 구현체는 SBCL 이며, 네이티브 코드로 직접 컴파일합니다. 빠르고, 오픈소스이며, 현대 하드웨어와 호환됩니다. 이를 사용하면 잘 작성된 Common Lisp 코드는 C나 Rust에 견줄 만한 성능을 낼 수 있습니다. SBCL은 최적화를 많이 하기 때문에 다른 구현체보다 컴파일 속도는 약간 느리지만, 리스프 계열에서 가장 빠른 코드 중 일부를 생성합니다.
음과 양의 Common Lisp 로고. 람다 기호 (λ)는 Alonzo Church가 만든 람다 계산법을 가리킵니다.
위에서 Common Lisp가 가장 포괄적인 방언이라고 했을 때, 저는 그것이 모든 리스프 중 가장 넓은 기능 집합을 제공한다는 뜻이었습니다. 이 언어는 기본으로 많은 기능을 제공하며, 그 상당수는 표준에 직접 정의되어 있습니다. 예를 들어 CL에는 언어 내부에서 컴파일과 평가를 제어하는 함수들(COMPILE, LOAD, EVAL, COMPILE-FILE 등)이 있습니다. 이는 제가 코드 안이나 REPL에서 이 함수들을 사용해 리스프 프로세스에 함수 컴파일, 파일 로드, 코드 평가를 지시할 수 있다는 뜻입니다. CL은 또한 DISASSEMBLE도 제공하는데, 이를 통해 컴파일된 함수에 대해 생성된 머신 코드를 살펴볼 수 있습니다.
Common Lisp에는 condition과 restart 시스템이 있는데, 이는 제가 본 프로그램 디버깅 및 검사 방식 중 가장 강력한 것 중 하나입니다. 실행 중 condition이 발생하면 리스프 프로세스가 멈추고, 그 시점의 프로그램 상태와 변수들을 살펴볼 수 있게 해 줄 수 있습니다. 그다음 프로그램을 재시작해서 연산을 다시 시도하거나, condition을 무시하고 실행을 계속하도록 선택할 수 있습니다. 이것이 가능한 또 다른 이유는 Common Lisp REPL이 실행 중인 시스템과 깊게 통합되어 있기 때문입니다. 프로그램이 원격 서버에서 실패하더라도 그 REPL에 접속해 살아 있는 프로세스를 검사하며 무엇이 잘못되었는지 파악할 수 있습니다.
Common Lisp는 함수형, 명령형, 메타프로그래밍, 객체지향 프로그래밍 같은 주요 프로그래밍 패러다임을 모두 지원합니다. 또한 CLOS라는 매우 고급 객체 시스템을 제공하며, 다중 디스패치와 제네릭 함수 같은 기능을 지원합니다. 그래서 Java나 C++ 같은 일반적인 객체지향 언어보다 OOP를 더 유연하게 사용할 수 있습니다. 또한 Common Lisp는 동적 타입 언어이지만, 선택적으로 타입 선언을 붙일 수 있는 풍부하고 표현력 있는 타입 시스템을 갖고 있다는 점도 중요합니다.
표준화는 언어가 안정적이라는 뜻입니다. 1994년의 ANSI Common Lisp는 오늘날에도 그대로 사용됩니다. 이 덕분에 리스프 사용자들은 하위 호환성 깨짐(backward incompatibility) 의 피해를 거의 입지 않으며, 오래된 Common Lisp 코드도 지금 완벽하게 돌아가는 경우가 많습니다. 예를 들어 1991년에 출간된 Peter Norvig의 오래된 리스프 책 PAIP을 살펴보면, 그 코드 상당수가 현대 구현체에서도 그대로 실행됩니다. Common Lisp에서는 Ruby나 Python 같은 언어에서 흔한 비호환성을 훨씬 덜 마주칩니다. 최근에 업데이트되지 않은 Common Lisp 라이브러리조차도 언어의 새 버전에 맞춰 계속 수정할 필요가 없기 때문에, 여러분의 시스템에서 여전히 잘 돌아가는 경우가 많습니다.
Common Lisp에는 더 새로운 언어들에서 흔해진 몇몇 편의 기능이 부족합니다. 예를 들어 더 다양한 자료구조를 위한 간결한 리터럴, 영속적 불변 컬렉션, 지연 시퀀스, 범용 패턴 매칭 내장 기능 등이 그렇습니다. Common Lisp는 1980년대에 여러 기존 리스프 방언을 하나의 언어로 통합하면서 설계되었습니다. 설계자들은 기존 리스프 프로그래머들이 완전히 새로운 구문을 다시 배울 필요 없이 받아들일 수 있도록, 예전 방언들의 구문과 패턴을 상당 부분 유지하기로 했습니다.
Common Lisp에는 중앙 지휘 체계나 기업 후원자가 없습니다. 커뮤니티는 비교적 작고, 여러 구현체, 프로젝트, 소통 채널에 분산되어 있습니다. 대부분은 이 언어를 계속 살아 있게 유지하려는 자원봉사자들로 이루어져 있습니다. 가이드와 도움말을 찾는 것은 다소 어려울 수 있습니다. 튜토리얼은 너무 단순하거나 너무 고급인 경우가 많고, 문서도 항상 쉽게 찾을 수 있는 것은 아닙니다.
현재 Rigetti Computing의 양자 컴퓨팅에 사용되고 있으며, Grammarly의 핵심 문법 서비스에도 사용된 바 있고, Google Flight Search도 구동합니다. 언급할 만한 사례로는 Steam에 출시된 오픈소스 비디오 게임 Kandria가 있으며, 전부 Common Lisp로 작성되었습니다.
흥미로운 사실 하나를 들자면, Paul Graham은 원래 HackerNews를 Racket 기반의 커스텀 리스프 방언 Arc로 작성했습니다. 오늘날 Daniel Gackle (@dang)이 이를 SBCL 위에서 실행되는 Arc의 Common Lisp 구현체인 Clarc로 다시 구현했고1, 하루 약 1천만 페이지를 서비스하고 있습니다.
CL은 주요 운영체제 전반에서 네이티브 코드로 컴파일되며 빠른 실행 성능을 달성할 수 있습니다. 대신 시작 시간은 약간 느릴 수 있으므로, 장시간 실행되는 프로세스에 아주 적합합니다. 리스프 방언들 중에서도 가장 강력한 REPL 중 하나를 제공하며, 학습 곡선은 가파르지만 짧은 시간 안에 견고한 소프트웨어를 작성하기에 가장 빠른 언어 중 하나라고 볼 수 있습니다.
CL은 양자 컴퓨팅처럼 빠른 반복이 필요한 연구와 프로토타이핑, 혹은 사양이 아직 완전히 정해지지 않았고 자주 바뀌어 소프트웨어가 빠르게 적응해야 하는 스타트업 환경에서 빛을 발합니다. Paul Graham은 Common Lisp가 자신의 인터넷 스타트업이 경쟁사를 이기는 데 어떻게 도움이 되었는지를 다룬 유명하고 영감을 주는 글을 썼습니다.
좋은 출발점으로는 Steve Losh의 A Road to Common Lisp가 있으며, 이 연재를 제가 쓰게 된 계기이기도 합니다. 초보자가 읽기 가장 쉬운 책 중 하나는 Peter Seibel의 Practical Common Lisp이며, 전체가 온라인에 공개되어 있습니다. 제가 읽은 언어 입문서 중 최고였는데, 가장 중요한 개념들이 장별로 깔끔하게 나뉘어 있기 때문입니다. 좀 더 장난스럽고 재미있는 입문서로는 Land of Lisp가 있으며, 웃긴 캐릭터와 만화를 이용해 게임을 통해 프로그래밍을 가르칩니다. 제가 즐겁게 읽은 좀 더 고급 책으로는 Paul Graham의 On Lisp가 있는데, 전적으로 매크로에 집중합니다.
CL에 가장 좋은 IDE 지원은 Emacs + Sly/Slime (Doom에는 이미 지원이 있습니다) 또는 Vim + VLime입니다. 더 약한 대안으로는 VSCode + Alive가 있습니다.
초보자에게 가장 좋은 자료 중 하나는 The Common Lisp Cookbook이며, 최신 튜토리얼이 많이 있습니다. 참고 자료와 라이브러리를 찾으려면 Awesome-CL 목록도 볼 만합니다. 저도 Common Lisp Quick Reference 소책자를 직접 출력해 두었는데, CL HyperSpec에서 정보를 찾는 것보다 더 빠를 때가 있습니다.
Rich Hickey는 안식년 동안 이것을 만들었습니다. 고객들이 호환성과 유지보수 문제를 걱정하며 Java와 C# 대신 Common Lisp를 쓰게 해 주지 않는 데 답답함을 느꼈기 때문입니다. 그래서 그는 JVM을 대상으로 하는 새로운 리스프 언어를 만들기로 했고, 이를 통해 Java 생태계가 수십 년 동안 쌓아 온 모든 것에 접근할 수 있게 하며 처음부터 실용적인 언어가 되도록 했습니다. 핵심은 Clojure가 언어를 제공하고, 호스트 플랫폼인 JVM이 런타임을 제공한다는 점입니다.
Clojure는 JVM 바이트코드로 직접 컴파일되므로, JVM에서 실행되는 어떤 코드베이스(Java, Kotlin, Scala)와도 호환됩니다. 이러한 호환성 덕분에 Clojure는 같은 코드베이스 안에서 다른 JVM 언어와 공존할 수 있고, Java 라이브러리를 직접 사용할 수 있으며, OS 이식성, 가비지 컬렉션, 빠르고 최적화된 런타임 등 JVM이 제공하는 모든 기능에 접근할 수 있습니다.
Rich Hickey는 Clojure를 위해 바퀴를 다시 발명하고 새 컴파일러를 처음부터 만들 필요가 없도록 JVM을 대상으로 하기로 했습니다. 그 대신 언어 설계와 자료구조에 관한 연구 논문을 읽는 데 집중하며, 가장 새롭고 효과적인 아이디어들 일부를 언어에 들여왔습니다.
Tom Hickey, 즉 Rich의 형제가 디자인한 Clojure 로고.
이 언어의 핵심은 불변 자료구조 위에서 동작하는 순수 함수로 이루어져 있습니다. 변경 가능한 객체를 변화하는 상태를 표현하는 주된 방식으로 삼는 대신, 상태 변화를 관리하기 위해 atoms, refs, agents 같은 명시적 메커니즘을 제공합니다. 이런 설계 선택 덕분에 Clojure는 함수형 프로그래밍 언어가 됩니다. 객체지향 프로그래밍도 지원하지만, 클래스 기반 상속 대신 프로토콜과 멀티메서드 같은 추상화를 사용합니다. 또한 구문도 Common Lisp보다 더 현대적이어서, 더 풍부한 리터럴, 구조 분해, 전반적으로 더 간결한 코드를 갖고 있습니다.
Clojure에는 몇 가지 새로운 기능도 있습니다. 영속적 자료구조는 구조적 공유를 사용해 불변 업데이트를 효율적으로 만듭니다. 또한 지연 시퀀스를 사용해 결과가 실제로 필요할 때만 계산이 수행되게 합니다. atoms, refs, agents, futures 같은 구성 요소를 통해 동시성 지원이 강력하며, 더 구조화된 데이터가 필요할 때는 records 와 사용자 정의 데이터 타입도 제공합니다. EDN(Extensible Data Notation)은 Clojure 리터럴에 기반한 단순하고 확장 가능한 데이터 형식으로, JSON, YAML 같은 것보다 더 리스프다운 대안입니다. Clojure에는 타입이 없지만, clojure.spec를 사용하면 정적 타입 시스템을 도입하지 않고도 데이터를 기술하고, 검증하고, 생성할 수 있어서 대규모 애플리케이션에 유용합니다.
Rich Hickey는 Common Lisp에서 표준화가 했던 역할과 비슷하게 안정성을 우선시하려는 의도로 Clojure를 설계했습니다. 언어는 활발히 개발되고 있지만, 새로운 버전으로 업데이트할 때 큰 폐기를 예상할 필요는 없습니다. 유지보수자들이 하위 호환성을 지키며, 새로운 기능도 주로 코어 라이브러리를 통해 추가하기 때문입니다.
Clojure는 다른 플랫폼 위에서 호스팅되도록 설계되었기 때문에, 브라우저에서 실행되는 JavaScript 엔진 같은 다른 런타임으로도 이식될 수 있습니다. ClojureScript 는 JVM이 아니라 JavaScript를 대상으로 하는 Clojure 컴파일러이므로, 같은 언어를 서버와 브라우저 양쪽에서 실행할 수 있습니다. 즉, Clojure를 풀스택 언어로 사용할 수 있습니다.
JVM 통합은 훌륭하지만, 몇 가지 대가도 따릅니다. 오류 메시지는 Java 구현 세부사항이 많이 섞여 있어서 항상 자명하지 않으며, 스택 트레이스도 문제를 디버깅할 때 꼭 도움이 되지는 않습니다. YourKit 같은 Java 프로파일러도 호환되지만, 그 안에서도 Java 클래스가 많이 보이기 때문에 그것들을 내가 쓴 코드와 다시 연결하는 일이 어려울 수 있습니다.
일반적으로 Clojure를 배우기 위해 Java를 알아야 할 필요는 없습니다. 저도 시작할 때 Java를 몰랐고, 그 때문에 큰 문제를 겪은 적은 없었습니다. 다만 서버에서 성능 튜닝을 할 때는 JVM을 조정하는 방법을 아는 것이 도움이 될 수 있습니다.
Clojure 주변 커뮤니티는 작지만 활발하며, 새로 들어오는 사람들이 언어를 빨리 익히도록 기꺼이 도와줍니다. 이들은 대체로 Clojurians 라고 불리며, 주로 Slack에 모여 있습니다. 그곳에서 오픈소스 프로젝트 협업도 하고 일자리 기회도 찾을 수 있습니다.
Clojure는 전 세계 여러 기업과 스타트업에서 활발히 사용되며, 아마도 프로덕션에서 가장 널리 쓰이는 리스프일 것입니다. 대표적인 이름으로는 Cognitect(Clojure 개발의 주요 후원자)를 인수한 Nubank(라틴아메리카 최대 규모 핀테크 기업 중 하나)를 비롯해 Walmart, Netflix, Apple, Salesforce, Amazon, Cisco, Grammarly 등이 있습니다.
Clojure는 대규모 소프트웨어 시스템, 특히 작업 부하가 많은 데이터 처리로 이루어져 있는 곳, 혹은 더 넓게 보면 예전 같으면 Java를 선택했을 만한 곳에서 빛을 발합니다. REPL을 통한 지속적인 반복 덕분에 코드를 빠르게 작성하고 시험할 수 있어, 더 짧은 시간 안에 신뢰할 수 있는 해법을 제공하는 데 적합합니다. 불변 중심 설계는 가장 까다로운 동시성 버그 일부를 제거하는 데 도움이 되며, 함수형 지향성은 프로그래머가 프로그램을 데이터 변환의 연속으로 작성하도록 이끕니다.
Clojure는 작업이 빠르게 일어나야 하고 대개 대량의 데이터를 처리해야 하는 금융과 트레이딩 같은 분야에 매우 잘 맞습니다. 또한 복잡한 로직을 가진 도메인 특화 문제를 푸는 스타트업에도 좋습니다. 언어의 확장성이 문제 중심으로 언어를 모델링할 수 있게 해 주기 때문입니다.
Clojure를 한번 시도해 보고 싶다면 Try Clojure를 먼저 보고, Clojure Koans 같은 연습문제를 풀어 본 뒤, 더 고급 내용으로 4Clojure를 해 보는 것이 좋습니다.
공식 웹사이트는 찾아볼 수 있는 자료 중에서도 가장 잘 정제된 자료들을 제공하며, 모든 핵심 기능과 그 특별한 설계 선택의 동기를 훌륭하게 설명합니다. Clojure는 또한 제가 어떤 언어에서 본 것들 중에서도 최고 수준의 문서 웹사이트를 갖고 있습니다. 코어 라이브러리 함수에는 모두 docstring이 있고, 커뮤니티가 사용 예시를 추가해 두었기 때문에 각 함수가 무엇을 하는지 빠르게 파악할 수 있습니다.
이 언어를 가장 잘 설득하는 소개 는 Clojure Distilled입니다. 아마 Clojure가 제공하는 것을 가장 짧게 설명한 자료 중 하나일 것입니다.
Clojure가 공개된 뒤, Rich Hickey는 소프트웨어 설계에 관한 일련의 강연으로 유명해졌습니다. 그는 그 안에서 소프트웨어 설계의 단순함, 상태와 시간, 데이터베이스, 동시성, 언어 설계 같은 근본 주제에 대한 깊은 전문성을 공유했습니다. 그의 최고 강연들로는 Simple Made Easy, The Value of Values, Hammock-Driven Development, Are We There Yet?, Design, Composition, and Performance가 있습니다. Clojure를 선택하든 아니든, 시간을 내어 꼭 보시길 강력히 권합니다.
좋은 책으로는 Daniel Higginbotham의 Clojure for the Brave and True가 있으며, 온라인에서 무료로 읽을 수 있고 초보자에게 좋습니다. 제가 구입한 책은 The Joy of Clojure인데, Java 같은 OOP 언어 출신 프로그래머에게 추천됩니다.
에디터로는 VSCode에 Clojure용 완전하고 풍부한 확장인 Calva가 있습니다. Emacs를 쓴다면 Cider를 사용할 수 있고, 대부분의 에디터/IDE용 확장도 उपलब्ध합니다.
Racket은 Scheme 에서 내려온 현대적인 방언입니다. 다른 Scheme 방언들이 대체로 작은 언어인 데 비해, Racket은 자신만의 고유한 기능을 갖춘 완전한 언어로 발전했습니다.
이 코드는 모든 주요 플랫폼에서 컴파일되며, 이 언어는 웹 애플리케이션, 그래픽 인터페이스, 데이터베이스 통합, 그리고 주로 그래픽을 사용하는 도구를 빠르게 개발하는 많은 분야에서 사용되어 왔습니다.
Racket 로고.
Racket은 언어 지향적(language-oriented) 입니다. 즉, 새로운 프로그래밍 언어를 만들도록 설계되었습니다. 모든 소스 파일은 #lang으로 시작하는데, 이것이 그 모듈에서 사용하는 언어를 정의합니다. Racket 생태계를 기반으로 두면서도, 고유한 구문과 의미론을 가진 완전히 새로운 언어를 만들 수 있습니다. 다른 리스프 방언들도 DSL을 쉽게 만들 수 있지만, 결국 리스프에 묶여 있습니다. Racket은 이 개념을 더 발전시켜, 반드시 리스프 구문을 쓰지 않아도 되는 완전히 새로운 언어를 쉽게 정의할 수 있게 합니다.
Racket은 또한 매우 다양한 기능을 제공합니다. 크로스플랫폼 GUI, 웹 서버, 동시성과 병렬성, 정규 표현식, 패턴 매칭, 클래스와 객체, 그리고 C 코드를 호출하기 위한 FFI 라이브러리를 포함합니다. 패키지 관리자는 생태계에 통합되어 있고, 방대한 라이브러리 모음을 즉시 사용할 수 있습니다. 다른 리스프 방언과 비교하면, 뭔가 본격적인 것을 만들기 시작하기 전에 필요한 준비가 대체로 더 적습니다.
Racket은 고급 매크로 시스템을 제공하며, 다른 방언들과 같은 종류의 매크로 지원에 더해 hygienic macro도 제공합니다. Hygienic 이라는 것은 매크로가 도입한 식별자가 주변 코드의 식별자를 우연히 포획하지 않는다는 뜻입니다. 그래서 특히 초보자에게 매크로를 더 안전하게 작성할 수 있게 하며, 다른 방언처럼 gensym에 의존할 필요가 없습니다.
정적 타입은 Typed Racket을 통해 지원됩니다. 이는 타입 주석을 추가하고 실행 전에 코드를 검사할 수 있게 해 주는 타입드 변형입니다. 타입드 모듈과 언타입드 Racket 모듈은 함께 동작할 수 있으므로, 전체 프로그램을 다시 쓰지 않고도 유용한 곳에만 타입을 도입할 수 있습니다.
Racket은 Scheme 전통에서 왔고 강한 함수형 프로그래밍 뿌리를 가졌지만, 여러 패러다임을 지원합니다. 프로그램은 명령형 프로그래밍, 객체와 클래스, 함수형 기법, 메타프로그래밍을 사용해 작성할 수 있습니다.
언어 설치에는 DrRacket이 함께 제공되는데, 이는 언어를 바로 사용할 수 있게 모든 것이 준비된 훌륭한 IDE이며 주요 플랫폼 전반에서 이용할 수 있습니다. 에디터, REPL, 디버거를 하나의 프로그래밍 환경으로 결합해 Racket 코드를 작성하고 시험하게 해 줍니다.
주된 단점은 작은 생태계와 산업계에서의 제한된 사용입니다. 더 인기 있는 언어들에 비해 라이브러리, 프로젝트, 일자리가 적습니다. 성능과 배포도 약점이 될 수 있으며, 특히 다른 컴파일형 방언과 비교하면 더 그렇습니다.
Racket은 새로운 프로그래밍 언어를 설계하고, 컴파일러와 인터프리터를 만들고, 새로운 언어 기능을 실험할 때 빛을 발합니다. 그래서 컴퓨터 과학 교육과 대학 연구에서 특히 인기가 높습니다. 또한 언어 지향적이기 때문에, 프로그램이 단순한 DSL을 제공해야 하는 경우, 예를 들어 사용자 정의를 위해 DSL이 필요하지만 사용자가 리스프 방언을 직접 쓰도록 강요하고 싶지 않을 때 좋은 선택입니다.
Racket은 프로토타이핑, 스크립팅, 그리고 포괄적인 라이브러리와 개발 도구를 즉시 사용할 수 있는 것이 중요한 프로젝트에 이상적입니다. 다른 방언들도 외부 라이브러리와 래퍼를 통해 GUI를 제공하지만, Racket에서는 그것이 기본으로 제공된다는 점도 언급할 가치가 있습니다.
Racket은 유용한 정보가 매우 많은 풍부한 웹사이트를 갖고 있습니다. 초보자에게 중요한 자료로는 Quick: An Introduction to Racket with Pictures와, 언어에 대한 완전한 가이드인 The Racket Guide가 있습니다. 또한 열정적인 기여자인 Matthew Butterick이 쓴, 시각적으로 잘 구성된 언어 안내서 Beautiful Racket도 언급할 만합니다. 컴파일러를 작성하고 싶다면 Racket을 사용하는 이 강의를 살펴보세요.
Scheme에 대해 이용할 수 있는 최고의 자료는 SICP, 일명 Wizard Book 입니다. 이 책은 리스프만 다루는 것은 아니지만, 프로그래밍과 프로그래밍 언어의 기초를 배우기에 훌륭한 책입니다. 또한 다소 독특한 책인 The Little Schemer도 언급할 가치가 있는데, 페이지의 답을 보기 전에 독자가 먼저 생각하도록 강제하는 긴 질문 연속으로 이루어져 있습니다.
Elisp는 Emacs의 일부인 특수 목적 방언으로, 에디터를 사용자화하는 데 쓰입니다. 제약도 꽤 많은 오래된 리스프 방언이지만, 범용 프로그래밍 언어로 쓰는 것 외에 제가 발견한 리스프의 가장 실용적인 활용 중 하나이기도 합니다. Emacs 자체의 많은 부분이 Elisp로 작성되어 있기 때문에, Emacs의 기능을 사용자화하고 확장하는 데 사용됩니다. Emacs 안에서 Elisp 코드를 평가하면 다시 로드할 필요 없이 실시간으로 Emacs의 모양과 동작 방식을 바꿀 수 있습니다.
An Introduction to Programming in Emacs Lisp는 온라인에서 무료로 볼 수 있습니다. 또 다른 유용한 자료로는 활발한 Emacs 기여자이자 전도자인 Protesilaos Stavrou의 Emacs Lisp Elements가 있습니다.
방언들 사이의 구문 차이를 보여 주기 위해 간단한 명령 인터프리터를 사용할 수 있습니다.
(defun calculate (instructions) (loop with result = 0 for (operation value) in instructions do (setf result (case operation (add (+ result value)) (subtract (- result value)) (multiply (* result value)))) finally (return result)))(calculate '((add 5) (multiply 3) (subtract 4))) ;; => 11
Common Lisp에서는 각 명령을 순회하고 구조 분해하기 위해 LOOP 매크로를 사용한 다음, 지역 상태를 갱신하면서 연산을 수행했습니다. 누산기(result)를 시퀀스를 따라 전달한다는 점에 주목하세요. 본질적으로 reduce와 같은 역할을 합니다.
(defn calculate [instructions] (reduce (fn [result [operation value]] (case operation :add (+ result value) :subtract (- result value) :multiply (* result value))) 0 instructions))(calculate [[:add 5] [:multiply 3] [:subtract 4]]) ;; => 11
Clojure에서는 명령 시퀀스에 대해 reduction을 사용했습니다. reduction이 불변 누적을 사용하기 때문에 지역 상태를 사용하거나 갱신하지 않는다는 점에 주목하세요. 또한 벡터 [], :add 같은 키워드, 그리고 [operation value]로 각 벡터의 내용을 구조 분해하는 식으로 Clojure 고유의 문법도 사용했습니다.
(define (calculate instructions) (for/fold ([result 0]) ([instruction (in-list instructions)]) (match instruction [(list 'add value) (+ result value)] [(list 'subtract value) (- result value)] [(list 'multiply value) (* result value)])))(calculate '((add 5) (multiply 3) (subtract 4))) ;; => 11
Racket에서는 for/fold를 사용해 명령들을 순회하면서 반복마다 결과를 다음 반복으로 전달합니다. 또한 match로 각 명령을 구조 분해하면서 동시에 어떤 연산인지 식별합니다. 예를 들어 (list 'add value)는 첫 번째 원소가 심볼 add인 2원소 리스트에 매치되며, 두 번째 원소를 value에 바인딩합니다.
위 내용을 요약하면 다음과 같습니다.
실무적으로 쓸 수 있는 실용적이고 우아한 리스프를 찾는 대부분의 프로그래머에게는 Clojure가 아마 가장 안전한 첫 선택일 것입니다.
전통적인 리스프 개발 모델을 가장 충실하게 경험하고 싶고, JVM에 의존하지 않는 네이티브 컴파일과 매우 상호작용적인 환경, 그리고 거대한 언어를 원한다면 Common Lisp를 선택하세요.
컴퓨터 과학 학생이거나, 컴파일러와 프로그래밍 언어 설계에 관심이 있거나, 도구를 빨리 작성할 방법이 필요하거나, 크로스플랫폼 GUI를 쉽게 쓰고 싶다면 Racket을 선택하세요. 방언들 중에서는 아마 이것이 시작하기 가장 쉬운 편입니다.
질문이나 피드백이 있다면 이메일을 보내 주세요.