이맥스 리스프
1. 개요
이맥스 리스프는 텍스트 편집기인 이맥스(Emacs)에서 사용되는 리스프(Lisp) 방언으로, 맥리스프(Maclisp)와 유사하며 Common Lisp의 영향을 받았다. 함수를 데이터로 취급하는 강력한 기능을 제공하며, 절차적 프로그래밍과 함수형 프로그래밍 방식을 지원한다. 이맥스 리스프는 동적 스코프를 기본으로 사용하며, 꼬리 재귀 최적화를 수행하지 않는 특징이 있다. 바이트 코드 컴파일을 통해 실행 속도를 향상시킬 수 있으며, cl-lib 패키지를 통해 Common Lisp의 기능을 사용할 수 있다. Emacs 24부터는 어휘적 스코프를 옵션으로 제공한다.
-
이맥스 -
리처드 스톨먼
리처드 스톨먼은 자유 소프트웨어 운동의 창시자이자 GNU 프로젝트 설립자로서, Emacs 편집기 개발, GNU 운영 체제 개발, 자유 소프트웨어 재단 설립, 카피레프트 개념 대중화, GNU 일반 공중 사용 허가서 개발 등 자유 소프트웨어 운동을 이끌었으나, 논쟁적인 발언으로 FSF 회장직에서 사임 후 복귀하기도 한 미국의 컴퓨터 프로그래머, 해커, 사회 운동가이다. -
이맥스 -
제임스 고슬링
제임스 고슬링은 자바 프로그래밍 언어 개발자로 가장 잘 알려진 캐나다 출신의 컴퓨터 과학자로, 썬 마이크로시스템즈 등 여러 회사를 거쳐 현재는 은퇴 후 다양한 기업의 고문 및 이사로 활동하고 있다. -
리스프 프로그래밍 언어 계열 -
클로저 (프로그래밍 언어)
클로저는 리치 히키가 개발한 JVM 기반의 함수형 프로그래밍 언어로, 자바와의 호환성을 특징으로 하며 불변 데이터 구조와 STM을 활용한 동시성 관리, 그리고 REPL 환경, 매크로 시스템 등의 기능을 제공한다. -
리스프 프로그래밍 언어 계열 -
커먼 리스프
커먼 리스프는 1980년대 초 여러 리스프 방언 통합 시도에서 시작된 언어로, S-표현식 문법, 다양한 자료형, 일급 함수, 매크로, CLOS를 특징으로 하며, ANSI 표준으로 정의되어 다양한 분야에서 활용된다. -
문서 편집기 -
맞춤법 검사기
맞춤법 검사기는 텍스트의 오타와 문법 오류를 검사하여 수정 제안을 제공하는 소프트웨어 도구이며, 1970년대에 처음 등장하여 기술 발전을 거쳐 현재 다양한 플랫폼에서 여러 언어를 지원한다. -
문서 편집기 -
HTML 편집기
HTML 편집기는 HTML 마크업 언어로 작성된 웹 페이지 소스 코드를 편집하는 소프트웨어로, 텍스트 기반 편집 방식과 WYSIWYG 방식이 있으며, 구문 강조 등의 기능을 제공하여 코딩 편의성을 높인다.
2. 다른 리스프 방언과의 비교
이맥스 리스프는 맥리스프(Maclisp)와 가장 밀접한 관련이 있으며, 이후 Common Lisp의 영향을 받았다. 이는 명령형 및 함수형 프로그래밍 방식을 지원한다. 리처드 스톨만은 Gosling Emacs를 GNU Emacs로 포크할 때, 함수를 데이터로 취급하는 기능을 포함한 강력한 기능 때문에 리스프를 확장 언어로 선택했다. 당시 Scheme도 존재했지만, 스톨만은 워크스테이션에서의 성능 문제와 더 쉽게 최적화할 수 있는 방언을 개발하고자 하는 의도로 이맥스 리스프를 선택했다.
이맥스 리스프는 애플리케이션 프로그래밍에 사용되는 현대적인 Common Lisp 및 Scheme 방언과는 실질적으로 다르다. 이맥스 리스프의 두드러진 특징은 기본적으로 렉시컬 스코프가 아닌 동적 스코프를 사용한다는 것이다. 즉, 함수는 호출된 스코프의 지역 변수를 참조할 수 있지만, 정의된 스코프의 지역 변수는 참조할 수 없다. 최근에는 렉시컬 스코핑을 사용하도록 코드를 업데이트하려는 노력이 진행되고 있다.
2.1. 리스프 방언 타임라인
| 1958 | 1960 | 1965 | 1970 | 1975 | 1980 | 1985 | 1990 | 1995 | 2000 | 2005 | 2010 | 2015 | 2020 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| LISP 1, 1.5, LISP 2 | ||||||||||||||
| 맥리스프 | ||||||||||||||
| 인터리스프 | ||||||||||||||
| MDL | ||||||||||||||
| Lisp Machine Lisp | ||||||||||||||
| Scheme | R5RS | R6RS | R7RS small | |||||||||||
| NIL | ||||||||||||||
| ZIL (Zork Implementation Language) | ||||||||||||||
| Franz Lisp | ||||||||||||||
| Common Lisp | ANSI 표준 | |||||||||||||
| Le Lisp | ||||||||||||||
| MIT Scheme | ||||||||||||||
| XLISP | ||||||||||||||
| T | ||||||||||||||
| Chez Scheme | ||||||||||||||
| 이맥스 리스프 | ||||||||||||||
| AutoLISP | ||||||||||||||
| PicoLisp | ||||||||||||||
| Gambit | ||||||||||||||
| EuLisp | ||||||||||||||
| ISLISP | ||||||||||||||
| OpenLisp | ||||||||||||||
| PLT Scheme | Racket | |||||||||||||
| newLISP | ||||||||||||||
| GNU Guile | ||||||||||||||
| Visual LISP | ||||||||||||||
| Clojure | ||||||||||||||
| Arc | ||||||||||||||
| LFE | ||||||||||||||
| Hy | ||||||||||||||
3. 예시
이맥스 리스프는 다재다능한 텍스트 편집기를 만들기 위한 데이터 구조와 기능을 제공하는 데 목표를 두고 있다. 예를 들어, 모드에 의해 정의된 문장, 단락 또는 더 높은 구문 수준에서 버퍼 텍스트를 탐색하고 수정하는 많은 기능을 제공한다.
이맥스 리스프 코드는 평가되는 즉시 적용된다. 재컴파일하거나, 이맥스를 다시 시작하거나, 구성 파일을 재해시할 필요가 없다. 코드가 이맥스 초기화 파일에 저장되면 이맥스는 다음에 시작할 때 확장을 로드한다. 그렇지 않으면 이맥스를 다시 시작할 때 변경 사항을 수동으로 다시 평가해야 한다.
3.1. 창 분할 예제
이맥스에서 편집 영역은 '버퍼'를 표시하는 여러 '창'으로 분할할 수 있다. `C-x 2` 키 바인딩을 누르면 새 창을 열 수 있는데, 이 때 이맥스 리스프 함수 `split-window-below`가 실행된다. 새 창에는 보통 이전 창과 같은 버퍼가 표시된다.
다음은 새 창에 다른 버퍼를 표시하는 이맥스 리스프 코드 예제이다.
```emacs-lisp
(defun my-split-window-func ()
(interactive)
(split-window-below)
(set-window-buffer (next-window) (other-buffer)))
(global-set-key (kbd "C-x 2") #'my-split-window-func)
```
`defun` 키워드는 `my-split-window-func`라는 새 함수를 정의한다. 이 함수는 `split-window-below`를 호출하여 창을 분할하고, `set-window-buffer`와 `next-window`, `other-buffer`를 통해 새 창에 다른 버퍼를 표시한다. `global-set-key`는 "C-x 2" 키 입력을 이 함수에 연결한다.
이 코드는 어드바이스 기능을 사용하여 작성할 수도 있다. 어드바이스를 사용하면 키 바인딩을 변경하지 않고도 기존 함수를 둘러싸는 래퍼를 만들 수 있다.
3.2. 어드바이스(Advice) 기능
이맥스 리스프는 기존 함수를 둘러싸는 래퍼를 만들 수 있게 해주는 '어드바이스(Advice)' 기능을 제공한다. 이 기능을 사용하면 사용자가 자체 함수를 정의하는 대신, 기존 함수의 동작을 변경할 수 있다.
예를 들어, 사용자가 기본 C-x 2 키 바인딩을 통해 새 창을 열 때, 새 창에 이전 창과 동일한 버퍼가 아닌 다른 버퍼를 표시하도록 만들 수 있다. `defadvice` 매크로를 사용하여 `split-window-below` 함수 실행 후에 다음 코드를 실행하도록 할 수 있다.
```emacs-lisp
(defadvice split-window-below
(after my-window-splitting-advice first () activate)
(set-window-buffer (next-window) (other-buffer)))
```
이 코드는 `split-window-below` 함수가 실행된 *후* (`after`)에 `my-window-splitting-advice`라는 이름의 사용자 정의 코드를 실행한다. 이 코드는 새 창(`next-window`)의 버퍼를 다른 버퍼(`other-buffer`)로 설정한다. 어드바이스는 원래 함수 *전*에 실행되거나, 원래 함수를 감싸거나, 어드바이스 결과에 따라 원래 함수를 조건부로 실행하도록 지정할 수도 있다.
이맥스 24.4부터는 `defadvice` 매크로 대신 `advice-add` 함수를 사용하는 새로운 방식이 도입되었다. 새로운 시스템을 사용하면 위와 동일한 기능을 다음과 같이 구현할 수 있다.
```emacs-lisp
(defun switch-to-next-window-in-split ()
(set-window-buffer (next-window) (other-buffer)))
(advice-add 'split-window-below :before #'switch-to-next-window-in-split)
```
이 코드는 `split-window-below` 함수가 실행되기 *전* (`:before`)에 `switch-to-next-window-in-split` 함수를 실행한다.
4. 소스 코드
이맥스 리스프 코드는 보통 "`.el`" 확장자를 가진 텍스트 파일로 파일 시스템에 저장된다. 사용자 초기화 파일은 예외적으로 "`.emacs`", "`.emacs.el`", ".emacs.d/init.el" 등으로 나타난다. 이맥스 프로그램의 인터프리터는 함수와 변수를 읽고 구문 분석하여 메모리에 저장하며, 이들은 편집기나 구성 파일을 다시 시작하거나 다시 로드하지 않고도 자유롭게 수정 및 재정의할 수 있다.
이맥스의 많은 기능은 필요할 때만 로드되는 패키지 또는 라이브러리 형태로 제공된다. 예를 들어, 프로그램 소스 코드에서 키워드를 강조 표시하는 라이브러리와 테트리스 게임을 하는 라이브러리가 있다. 각 라이브러리는 하나 이상의 이맥스 리스프 소스 파일을 사용하여 구현된다.
4.1. 원시 함수
Emacs 개발자는 특정 함수를 C로 작성하는데, 이를 원시 함수(내장 함수 또는 subrs라고도 한다)라고 부른다. 원시 함수는 Lisp 코드에서 호출할 수 있지만, 수정하려면 C 소스 파일을 편집하고 다시 컴파일해야 한다. GNU Emacs에서 원시 함수는 외부 라이브러리로 사용할 수 없으며, Emacs 실행 파일의 일부이다. XEmacs에서는 운영 체제의 동적 링크 지원을 사용하여 이러한 원시 함수를 런타임에 로드할 수 있다. 원시 함수는 Emacs Lisp에서 사용할 수 없는 외부 데이터 및 라이브러리에 접근해야 하거나, C와 Emacs Lisp 간의 속도 차이가 중요할 만큼 자주 호출될 때 작성된다.
하지만 C 코드의 오류는 세그멘테이션 오류나 편집기를 충돌시키는 더 미묘한 버그를 쉽게 유발할 수 있다. 또한 Emacs Lisp 가비지 수집기와 올바르게 상호 작용하는 C 코드를 작성하는 것은 오류가 발생하기 쉽다. 따라서 원시 함수로 구현된 함수의 수는 필요한 최소한으로 유지된다.
5. 바이트 코드
'바이트 코드 컴파일'은 이맥스 리스프 코드의 실행 속도를 향상시킬 수 있다. 이맥스에는 이맥스 리스프 소스 파일을 바이트 코드라는 특수한 표현으로 변환할 수 있는 컴파일러가 포함되어 있다. 이맥스 리스프 바이트 코드 파일은 파일 이름 확장자 "`.elc`"를 갖는다. 소스 파일에 비해 바이트 코드 파일은 더 빠르게 로드되고 실행되며, 더 적은 디스크 공간을 차지하고, 로드 시 더 적은 메모리를 사용한다.
바이트 코드는 여전히 원시 함수보다 느리게 실행되지만, 바이트 코드로 로드된 함수는 쉽게 수정하고 다시 로드할 수 있다. 또한, 바이트 코드 파일은 플랫폼에 독립적이다. 이맥스와 함께 배포되는 표준 이맥스 리스프 코드는 바이트 코드로 로드되지만, 사용자의 참조를 위해 일치하는 소스 파일도 일반적으로 제공된다. 사용자가 제공하는 확장 기능은 크기가 크거나 계산 집약적이지 않기 때문에 일반적으로 바이트 코드 컴파일되지 않는다.
6. 언어 기능
이맥스 리스프는 맥리스프(Maclisp)와 가장 관련이 깊으며, 이후 Common Lisp의 영향을 받았다. 명령형 및 함수형 프로그래밍 방식을 모두 지원한다. 리스프는 EINE 및 ZWEI와 같은 이맥스 파생 제품의 기본 확장 언어였다. 리처드 스톨만은 Gosling Emacs를 GNU Emacs로 포크했을 때, 함수를 데이터로 취급하는 기능을 포함한 강력한 기능 때문에 리스프를 확장 언어로 선택했다. 당시 Common Lisp 표준은 아직 제정되지 않았고, Scheme이 존재했지만, 스톨만은 워크스테이션에서 성능이 떨어져 사용하지 않기로 결정하고, 더 쉽게 최적화할 수 있는 방언을 개발하고자 했다.
이맥스에서 사용되는 리스프 방언은 애플리케이션 프로그래밍에 사용되는 보다 현대적인 Common Lisp 및 Scheme 방언과 실질적으로 다르다. 이맥스 리스프의 두드러진 특징은 기본적으로 렉시컬이 아닌 동적 스코프를 사용한다는 것이다. 즉, 함수는 함수가 호출된 스코프의 지역 변수를 참조할 수 있지만, 정의된 스코프의 지역 변수는 참조할 수 없다. 최근에는 렉시컬 스코핑을 사용하도록 코드를 업데이트하려는 노력이 진행되고 있다.
"cl-lib" 패키지는 Common Lisp의 상당 부분을 구현하며, 기존 "cl" 패키지를 대체한다. "cl-lib" 패키지는 Emacs Lisp 스타일 가이드라인을 따르며, "cl-" 접두사를 사용하여 이름 충돌을 피한다. Emacs 24에서는 정적 스코프를 사용할 수 있게 되었다.
6.1. 꼬리 재귀 최적화
Emacs Lisp는 다른 일부 Lisp 구현과 달리 꼬리 호출 최적화를 수행하지 않는다. 이 때문에 꼬리 재귀는 스택 오버플로우를 일으킬 수 있다.
6.2. 이식성
apel 라이브러리는 폴리실라비(polysylabi) 플랫폼 브리지의 도움을 받아 이식 가능한 Emacs Lisp 코드를 작성하는 데 도움을 준다.
7. 동적 스코프에서 어휘적 스코프로의 전환
이맥스 리스프는 맥리스프(Maclisp)와 마찬가지로 동적 스코프를 사용하며, 버전 24부터 정적(또는 어휘적) 스코프를 옵션으로 제공한다. 파일 로컬 변수 `lexical-binding`을 설정하여 어휘적 스코프를 활성화할 수 있다.
원래 동적 스코핑은 최적화를 위한 의도였다. 당시에는 어휘적 스코핑이 흔하지 않았고 성능도 불확실했다. 컴퓨터 과학자 올린 시버스(Olin Shivers)에 따르면, 리처드 스톨만(RMS)은 이맥스 리스프에 동적 스코핑을 사용한 이유로 "어휘적 스코핑은 너무 비효율적"이라고 답했다고 한다. 또한 동적 스코핑은 사용자 정의에 더 큰 유연성을 제공하는 목적도 있었다.
그러나 동적 스코핑에는 몇 가지 단점이 있었다. 첫째, 서로 다른 함수 내 변수 간의 의도하지 않은 상호 작용으로 인해 대규모 프로그램에서 버그가 쉽게 발생할 수 있었다. 둘째, 동적 스코핑 하에서 변수에 접근하는 것은 일반적으로 어휘적 스코핑 하에서보다 느렸다. 이러한 단점을 해결하기 위해 어휘적 스코프로의 전환이 이루어졌다.