Lisp로 가는 길: 왜 Lisp인가

1 hour ago 1
  • 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 같은 개별 기능 하나가 아니라 그 조합에서 나옴
Read Entire Article