- Lisp는 괄호 문법보다 언어를 문제에 맞게 확장하는 사고방식이 핵심이며, REPL·패키지·심볼·조건·재시작까지 익혀야 진가가 드러남
- 코드와 데이터가 모두 리스트로 표현되는 동형성 덕분에, 매크로는 평가 전 코드를 데이터처럼 다뤄 새 제어 구조와 문법 추상화를 만들 수 있음
- Common Lisp에 없는 while도 defmacro로 추가할 수 있지만, 함수로 흉내 내면 인자가 즉시 평가되어 body가 코드가 아니라 값이 되는 차이가 생김
- Lisp 개발은 실행 중인 프로세스에 코드를 계속 평가하는 REPL 주도 개발에 가까워, 함수·매크로·변수를 재정의하며 프로그램을 중단 없이 진화시킬 수 있음
- 언어 확장성과 라이브 시스템이 결합되면 내부 DSL을 사용자에게 노출하는 확장 가능한 소프트웨어를 만들 수 있고, AutoLISP와 Emacs가 대표 사례임
Lisp가 요구하는 다른 프로그래밍 감각
- Lisp를 처음 접한 프로그래머는 괄호가 많은 문법, 독특한 들여쓰기, format의 첫 번째 인자로 표준 출력을 지정하는 방식에서 낯섦을 느낌
- Lisp를 익히려면 코드 읽기뿐 아니라 패키지와 심볼, 프로젝트 생성, 라이브러리 가져오기, REPL, 조건과 재시작까지 다뤄야 함
- 더 큰 변화는 알고리듬을 구성하는 방식에서 나타남
- Lisp는 다른 언어에서 하기 어려운 일을 가능하게 만들고, 알고리듬의 형태도 달라지게 함
- 문제에 맞춰 언어를 먼저 키운 뒤, 그 언어로 프로그램을 작성하는 흐름으로 이어짐
- Blub paradox는 덜 강력한 언어만 써 본 프로그래머가 Lisp의 강점을 알아보기 어려운 이유를 설명하는 개념임
확장 가능한 언어
- Lisp는 스스로를 확장할 수 있으며, Lisp 전문가들은 이를 programmable programming language라고 부름
- 핵심 도구는 macro임
- C, Rust, Swift의 매크로처럼 반복 코드 제거에만 머물지 않음
- Lisp 매크로는 언어의 일부가 되는 새 구성 요소를 만들 수 있음
- Common Lisp에는 기본 while 연산자가 없지만, defmacro로 직접 추가 가능함
(defmacro while (condition &body body)
`(loop while ,condition do
(progn ,@body)))
- 이 while 매크로는 condition이 참인 동안 body의 여러 명령을 실행함
- loop는 복잡한 반복을 작성하는 매크로임
- progn은 여러 표현식을 순서대로 실행하고 마지막 표현식의 결과를 반환함
- 결과적으로 (loop while ... do (progn ...)) 대신 (while ...) 형태를 쓸 수 있어, 코드가 짧아지고 C의 while과 비슷한 구조가 됨
매크로와 함수의 결정적 차이
- 같은 동작을 함수 fake-while로 만들면 실패함
(defun fake-while (condition body)
(loop while condition do
(funcall body)))
- fake-while 호출에서는 인자가 먼저 평가됨
- condition은 참값 t가 됨
- progn 내부의 (print counter)가 실행되고 (decf counter)가 2를 반환함
- body는 코드 블록이 아니라 값 2가 됨
- 이후 funcall은 함수를 기대하지만 2를 받기 때문에 The value 2 is not of type FUNCTION 같은 타입 오류가 발생함
- 반면 매크로 while은 condition과 body를 즉시 평가하지 않고 보존함
- macroexpand-1로 확장 결과를 확인할 수 있음
- 컴파일러는 확장된 (LOOP WHILE ... DO (PROGN ...)) 형태의 코드를 받음
- 매크로는 함수와 달리 인자를 미리 평가하지 않고 순수한 데이터로 다루기 때문에 이런 변환이 가능함
리스트, s-expression, 동형성
- Lisp 프로그램은 symbolic expression, 즉 s-expression의 연속으로 구성됨
- s-expression은 두 가지로 나뉨
- 원자(atom): 숫자, 문자열, 심볼 같은 데이터 단위
- 리스트(list): 원자나 다른 리스트를 담는 컬렉션
- Lisp라는 이름은 LISt Processing에서 왔고, 프로그램을 구성하는 명령들도 리스트로 표현됨
- Lisp에서 리스트는 주요 자료구조이면서 코드 작성 방식이기도 함
- 코드와 데이터가 모두 리스트로 표현되는 성질을 동형성(homoiconicity) 이라고 함
- 같은 (+ 1 2) 리스트도 평가 여부에 따라 다르게 동작함
- (+ 1 2)는 코드로 평가되어 3을 반환함
- '(+ 1 2)는 평가되지 않고 데이터 리스트 그대로 반환됨
- Lisp는 Polish notation을 사용해 함수 이름을 리스트의 첫 요소에 두고, 뒤에 인자를 둠
- 코드와 데이터의 경계가 얇기 때문에 매크로는 소스 코드를 변환할 수 있고, 프로그램을 쓰는 프로그램을 만들 수 있음
매크로가 만드는 코드 추상화
- 언어를 확장하면 특정 프로그램 영역에 더 표현력 있는 새 구성 요소를 만들 수 있음
- 반복되는 보일러플레이트는 커스텀 매크로로 대체해 코드를 줄이고 시간을 절약함
- 새 제어 구조를 만들어 리소스 접근과 폼 평가를 제어할 수 있음
- 매크로는 코드를 생성하고 성능을 개선하며, 복잡하고 오류가 나기 쉬운 코드를 감추는 문법 추상화를 제공함
Lisp는 라이브 시스템
- Lisp는 단순한 프로그래밍 언어라기보다 실행 중인 라이브 시스템으로 다뤄짐
- 일반적인 언어에서는 에디터에서 코드를 작성한 뒤 컴파일하고 실행해 관찰·테스트·디버깅하는 흐름이 많음
- Lisp에서는 먼저 Lisp 프로세스를 시작하고, REPL에 붙인 뒤, 프로젝트를 그 프로세스 안에 로드함
- REPL은 Read-Eval-Print Loop의 약자로, 코드를 평가하고 즉시 결과를 보는 대화형 환경임
- 현재 프로그램을 실행 중인 Lisp 프로세스를 들여다보는 창 역할을 함
- Lisp 개발자는 실행 중인 프로세스 안에서 코드를 계속 평가하고 결과를 REPL에서 바로 확인함
- 개별 함수 테스트
- 데이터베이스 질의
- 변수 검사
- 디버깅 같은 작업도 REPL에서 수행 가능함
- Lisp 프로세스는 멈출 필요가 없어 몇 주 동안 살아 있을 수 있음
- 함수, 매크로, 변수는 내부 메모리인 환경(environment)에 계속 정의·재정의되며, 이런 흐름을 REPL 주도 개발이라고 부름
Lisp에서의 핫 리로딩
- 웹 프런트엔드에서는 파일 저장이나 페이지 새로고침을 통해 JavaScript 코드가 핫 리로드되는 경험이 흔함
- 일반적인 핫 리로딩은 파일 감시, 변경 모듈 재컴파일, 웹소켓을 통한 변경 주입 같은 별도 도구와 기법에 의존함
- 모든 소프트웨어 스택이 같은 수준의 핫 리로딩을 제공하지는 않음
- 일부 백엔드 프레임워크는 재컴파일이 필요함
- 데스크톱과 모바일 개발 환경은 상황이 섞여 있음
- 저수준 언어는 매번 전체 컴파일이 필요함
- Lisp에서는 핫 리로딩이 별도 도구가 아니라 라이브 환경에서 코드를 평가할 때 자연스럽게 따라옴
- 심볼을 재정의하면 함수, 데이터, 매크로 모두 새 정의를 사용함
- 멈추고 컴파일하거나, 테스트를 별도로 실행하거나, 특수한 디버거를 쓸 필요가 줄어듦
- Lisp 프로그래밍은 프로그램을 조립한다기보다 진화시키는 방식에 가까움
확장 가능한 소프트웨어
- 언어 확장성과 라이브 시스템은 Lisp에서 확장 가능한 소프트웨어를 만들기 쉽게 함
- 일반적인 데스크톱 프로그램은 플러그인이나 스크립팅 언어를 통해 확장됨
- 개발자는 확장 시스템을 설계하고 유지해야 함
- 사용자는 플러그인이나 확장 작성을 위한 특정 API를 배워야 함
- Lisp에서는 매크로로 동작을 캡슐화하고 재사용하기 쉽게 만들면서 자연스럽게 DSL을 작성하게 됨
- 소프트웨어를 확장 가능하게 만들려면 내부에서 만든 DSL을 사용자에게 쓰게 하면 됨
- CMS 서버 예시에서는 서버 사이드 웹페이지 생성을 위한 html 매크로를 사용자에게 노출할 수 있음
- 사용자는 Lisp 변수, dolist 반복, format 문자열 포매팅을 그대로 함께 사용할 수 있음
- 전통적 템플릿 언어처럼 {{ user_name }}이나 {% for %} 같은 별도 문법을 배울 필요가 없음
- 이 DSL은 Lisp의 조건문, 재귀, 고차 함수, REPL 단계별 디버깅 능력을 그대로 물려받음
- 수학 그래프 소프트웨어 예시에서는 수식과 그리기를 위한 DSL을 노출해, 사용자가 곡선과 점을 적은 코드로 그릴 수 있음
실제 사례와 Lisp의 위치
- AutoCAD는 반복 작업 자동화와 복잡한 기하 생성에 AutoLISP를 사용함
- Emacs는 자체 Lisp 방언으로 대부분 구현되어 높은 확장성을 확보한 텍스트 에디터임
- PDF 리더
- 웹 브라우저
- 이메일 클라이언트
- RSS 리더
- 터미널 멀티플렉서
- Git 클라이언트
- 음악 플레이어
- 채팅 클라이언트
- 스프레드시트
- IRC 클라이언트
- 데스크톱 창 관리자까지 확장된 사례가 나열됨
- R. M. Stallman은 Lisp를 강력하고 우아한 프로그래밍 언어의 의미를 이해하게 해주는 언어로 평가함
- Lisp가 언젠가 기본 언어처럼 널리 쓰일 것이라는 기대는 실현되지 않았고, 앞으로도 그럴 가능성은 낮아 보임
- 그럼에도 Lisp는 1960년대부터 살아남아 Fortran 다음으로 오래된 현역 프로그래밍 언어가 됨
- Lisp의 가치는 확장성, 대화형 환경, REPL 같은 개별 기능 하나가 아니라 그 조합에서 나옴