---
title: "괄호가 싫어도 한 번은 봐야 할 작은 리스프, 재닛"
published: 2026-06-02T09:34:31.000Z
canonical: https://jeff.news/article/3615
---
# 괄호가 싫어도 한 번은 봐야 할 작은 리스프, 재닛

글쓴이는 취미 프로젝트용 언어로 작은 리스프 계열 언어인 재닛을 몇 년째 쓰고 있고, 무료 책까지 쓸 정도로 꽂혔다. 핵심은 문법 장난이 아니라 작은 런타임, 네이티브 실행 파일 배포, 파싱 표현 문법, 셸 스크립팅, 매크로, 컴파일 타임 실행이 한데 묶인 실용성이다.

- 글쓴이는 취미 프로젝트용 언어로 작은 리스프 계열 언어인 재닛에 제대로 꽂혔다고 함
  - 그냥 ‘괄호가 예뻐서’가 아니라, 직접 무료 책까지 썼을 정도로 실사용 경험이 쌓인 상태에서 나온 추천임
  - 글의 목표도 명확함. 리스프를 좋아하는 사람 말고, 아직 안 믿는 개발자에게 “왜 한 번쯤 써볼 만한가”를 설득하는 글임

- 재닛은 일부러 작게 만든 언어임
  - 명령형 언어고, 일급 함수, 단일 식별자 네임스페이스, 렉시컬 블록 스코프를 갖고 있음
  - 언어 코어는 `do`, `def`, `var`, `set`, `if`, `while`, `break`, `fn` 딱 8개 명령으로 구성됨
  - 대신 매크로가 있어서 고수준 제어 흐름이나 편의 문법은 얇은 래퍼로 얹을 수 있음

- 배우는 진입 장벽은 꽤 낮다고 봄
  - 글쓴이는 런타임 의미론을 “자바스크립트에서 이상한 함정을 빼고 값 타입을 더한 느낌”이라고 설명함
  - 표준 라이브러리 전체가 한 페이지에 들어갈 정도로 작아서, 오후 하나면 언어의 감을 잡을 수 있다는 주장임
  - 이 작고 익숙한 구조가 글쓴이를 처음 끌어들인 이유였다고 함

- 재닛의 강점 중 하나는 배포가 쉽다는 점임
  - 재닛 프로그램은 재닛 런타임을 정적으로 링크한 네이티브 실행 파일로 컴파일할 수 있음
  - 받는 사람에게 재닛 설치, 의존성 설치, 런타임 설치를 요구하지 않아도 됨
  - 심지어 그 프로그램이 재닛으로 작성됐다는 사실을 말하지 않아도 됨. 이건 작은 명령줄 도구 배포할 때 꽤 큰 장점임

- 내부 방식도 제법 우아함
  - 재닛은 자기 코드를 바이트코드로 컴파일한 뒤, 그 바이트코드를 재닛 런타임을 시작하는 `.c` 파일 안에 써 넣음
  - 그 다음 시스템의 C 컴파일러로 이 파일을 컴파일함
  - 재닛 자체가 임베딩하기 쉽게 설계됐기 때문에, 자기 자신을 작은 C 실행 파일 안에 임베드하는 식으로 동작하는 셈임

> [!IMPORTANT]
> 단순한 `hello world` 네이티브 바이너리가 재닛 1.27.0 기준 애플 실리콘 맥에서 784KB였다고 함. 이 안에 런타임, 가비지 컬렉터, 바이트코드 컴파일러까지 들어감.

- 텍스트 파싱 쪽은 글쓴이가 거의 과장처럼 칭찬하는 영역임
  - 재닛은 정규식 대신 파싱 표현 문법을 중심으로 텍스트를 다룸
  - 파싱 표현 문법은 멀티라인 텍스트, HTML, JSON 같은 비정규 언어, 심지어 임의의 널 바이트가 들어간 바이너리 포맷도 다룰 수 있음
  - 그냥 패턴 매칭이 아니라 구조적이고 조합 가능한 일급 파서라는 점이 핵심임

- 셸 스크립팅도 꽤 독특함
  - `sh`라는 서드파티 라이브러리가 있고, 파이프와 리다이렉트를 재닛 코드 안에서 직접 표현할 수 있음
  - 예시는 `($ find . -name *.janet | say)`처럼 생겼음
  - 글쓴이는 이 라이브러리 때문에 재닛이 펄 대안뿐 아니라 꽤 많은 경우에 배시 대안으로도 보인다고 말함

- 재닛은 임베디드 언어로도 밀고 있음
  - 런타임이 작은 C 라이브러리라서 링크한 뒤 일반 C 함수로 재닛 값을 조작하면 됨
  - 앱에 스크립팅 인터페이스를 붙이거나, 웹사이트에 임베드해서 커스텀 프로그래머블 도메인 특화 언어를 만들 수도 있음
  - 글쓴이는 루아가 임베디드 언어의 사실상 표준이 된 걸 아쉬워하면서, 재닛도 이 영역에서 충분히 매력적이라고 봄

- 컬렉션은 가변과 불변을 둘 다 기본으로 제공함
  - 불변 컬렉션은 값 의미론을 가짐. 예를 들어 `[1 2]`와 `(take 2 [1 2 3])`는 메모리 주소가 달라도 같은 값으로 취급됨
  - 가변 컬렉션은 참조 의미론을 가짐. 같은 키와 값을 가진 해시 테이블이라도 서로 다른 객체면 구분됨
  - 모든 언어가 복합 불변 값을 표준 라이브러리 안에 자연스럽게 넣어두진 않기 때문에, 이 부분도 글쓴이에겐 큰 장점임

- 진짜 리스프 맛은 매크로에서 나온다고 함
  - 글쓴이는 사실 이게 재닛을 배워야 하는 진짜 이유라고 보지만, 초반에 말하면 독자가 도망갈까 봐 뒤로 미뤘다고 함
  - 매크로를 안 배워도 재닛 코드는 쓸 수 있지만, 배우면 “코드를 쓰는 코드”를 작성하는 특유의 재미가 생김
  - 컴파일 타임에 실행되는 코드와, 그 코드가 만들어낼 런타임 애플리케이션 코드를 동시에 머릿속에 들고 있어야 해서 사고방식이 꽤 달라짐

- 재닛 매크로는 위생적 매크로가 아니고, 함수 전용 네임스페이스도 없음
  - 그런데 리터럴 함수를 언쿼트할 수 있게 해서 참조 투명한 매크로를 만들 수 있다고 함
  - 글쓴이는 이걸 섬세한 문제에 대한 굉장히 단순하고 우아한 해법으로 봄
  - 여기서 재닛의 더 큰 특징인 “컴파일 타임 값을 런타임으로 넘기는 능력”으로 이야기가 이어짐

- 재닛은 컴파일할 때 실행한 결과를 스냅샷으로 저장할 수 있음
  - 프로그램을 컴파일하면 최상위 명령들을 실행하고, 실행이 끝난 뒤 프로그램 상태의 스냅샷을 디스크에 기록함
  - 공유 참조도 보존되고, 가변 값은 스냅샷을 재개한 뒤에도 계속 변경 가능함
  - 제너레이터는 다음에 재개할 명령 위치를 기억하고, 클로저도 그대로 살아 있음

- 이 기능은 매크로 없이도 꽤 강력함
  - 게임 자산을 미리 계산하거나, 파일을 컴파일 타임에 읽어서 최종 바이너리에 임베드할 수 있음
  - 글에서는 SQL 스키마 파일을 읽고 데이터베이스 바인딩을 자동 생성하는 예시도 언급함
  - 대부분의 언어에서는 빌드 스크립트나 별도 코드 생성 도구로 빼야 할 작업을 언어 안에서 자연스럽게 처리하는 느낌임

- 문법 취향도 글쓴이에겐 큰 매력 포인트임
  - 괄호를 많이 쓰지만 리스트는 `[]`, 테이블은 `{}`를 써서 단조로움을 줄임
  - 가변 리터럴은 `@"mutable string"`처럼 항상 `@` 접두사를 붙임
  - 익명 함수는 `(fn [x] (+ 1 x))`로 쓰고, `|(+ 1 $)`처럼 표현식을 함수로 들어 올리는 축약 문법도 있음
  - 백틱 문자열은 원하는 개수의 백틱으로 감싸서 이스케이프 고민 없이 문자열 내용을 넣을 수 있음

- 재닛은 전통보다 편안함을 택한 리스프임
  - `CAR` 대신 `first`, `PROGN` 대신 `do`, `LAMBDA` 대신 `fn`, `SETQ` 대신 `def`를 씀
  - `nil`은 빈 리스트가 아니라 별도 타입이고, 불리언도 일급 값임
  - 링크드 리스트 중심 전통도 거의 보이지 않음. 리스프 계열이지만 옛 관습을 숭배하는 언어는 아니라는 얘기임

---
## 기술 맥락

- 재닛의 선택은 “작은 언어를 어디에 쓸 것인가”라는 질문에 꽤 선명하게 답해요. 거대한 애플리케이션 플랫폼이 아니라, 배포 가능한 명령줄 도구와 임베디드 스크립팅, 텍스트 파서 쪽에서 강점을 잡은 거예요.

- 네이티브 실행 파일 배포가 중요한 이유는 사용자 경험 때문이에요. 작은 도구 하나 쓰자고 런타임과 패키지 매니저를 설치하라고 하면 바로 마찰이 생기거든요. 재닛은 런타임까지 묶어서 1MB 안팎 바이너리로 내보낼 수 있으니 이 부담을 줄여요.

- 정규식 대신 파싱 표현 문법을 전면에 둔 것도 흥미로운 선택이에요. 로그 몇 줄 찾는 수준을 넘어 설정 파일, 마크업, 바이너리 포맷처럼 구조가 있는 입력을 다루려면 정규식은 금방 지저분해지거든요. 재닛은 이 작업을 언어의 기본 감각에 가깝게 끌어올린 셈이에요.

- 컴파일 타임 실행과 스냅샷은 빌드 도구와 런타임 코드의 경계를 흐리게 만들어요. 파일을 미리 읽고, 바인딩을 생성하고, 계산 결과를 바이너리에 넣는 식의 작업을 별도 파이프라인 없이 언어 안에서 처리할 수 있기 때문이에요.

## 핵심 포인트

- 재닛은 핵심 명령이 8개뿐인 작은 명령형 리스프 계열 언어다.
- 재닛 프로그램은 런타임까지 정적으로 묶은 네이티브 실행 파일로 쉽게 배포할 수 있다.
- 정규식 대신 파싱 표현 문법을 기본 텍스트 처리 도구처럼 써서 멀티라인 텍스트, 파일 형식, 바이너리까지 다루기 좋다.
- 컴파일 시점에 값을 만들고 실행 시점으로 넘길 수 있어 자산 임베딩이나 코드 생성 같은 작업이 자연스럽다.

## 인사이트

이 글의 재미는 재닛을 ‘리스프니까 멋짐’으로 밀지 않는다는 데 있음. 작은 명령줄 도구, 파서, 임베디드 스크립팅처럼 개발자가 실제로 귀찮아하는 영역을 꽤 정면으로 찌른다.
