Lisp: 아이싱인가 케이크인가?
(dthompson.us)- Spring Lisp Game Jam 2024에는 48개 게임이 제출돼 새 기록을 세웠고, 참가작은 Lisp를 얹는 방식과 스택 자체를 Lisp로 만드는 방식으로 뚜렷하게 갈림
- 아이싱 방식은 C/Rust/Lua 기반 프로그램 위에 Lisp를 스크립팅 층으로 올려 빠르게 결과를 내지만, 아래쪽 정적 언어와 툴체인에 강하게 묶임
- 케이크 방식은 프로그램 대부분을 Lisp로 작성하고 C FFI를 최소화해 더 깊은 제어권을 얻지만, 라이브러리 구현·래퍼 작성·웹 배포에서 비용이 커짐
- Game Jam에서는 Fennel+love2d와 S7+raylib가 아이싱에 가깝고, Guile+Chickadee는 케이크이며, Hoot+HTML5 canvas는 Scheme 기반 Wasm 툴체인 덕분에 케이크에 더 가까움
- Lisp 비중이 커질수록 라이브 해킹, 메모리 안전성, Lisp/C 경계 감소, 해킹 가능성이 늘어나며 Guix·Trial·Pre-Scheme 같은 프로젝트도 같은 방향을 보여줌
Spring Lisp Game Jam 2024의 제출 현황
- Spring Lisp Game Jam 2024는 일주일 전에 끝났고, 48개 게임이 제출돼 잼 기록을 새로 세움
- 참가자들은 이후 일주일 동안 서로의 게임을 플레이하고 평가함
- 제출작의 언어별 분포는 다음과 같음
- Guile: 15개, 31%
- Fennel: 10개, 21%
- Clojure: 5개, 10%
- Common Lisp: 5개, 10%
- Racket: 4개, 8%
- Elisp: 4개, 8%
- S7: 3개, 6%
- Kawa: 1개, 2%
- Owl: 1개, 2%
- Guile 비중: {p:31}
- Scheme 구현체를 하나의
scheme범주로 묶지 않은 이유는 Scheme 명세가 작고, Guile·Racket·S7·Kawa가 서로 다른 목적의 구현체이기 때문임 - 이번 잼에서는 Guile이 처음으로 가장 많은 제출작을 기록함
- Guile 게임 15개 중 11개는 Hoot로 만든 웹 게임임
- Hoot는 Spritely Institute에서 개발 중인 Scheme-to-WebAssembly 컴파일러임
- 11개 중 2개는 공식 Spritely 프로젝트임
- Spritely Institute가 잼 시작 전에 Hoot로 게임을 만들어 보라고 요청했고, 많은 참가자가 응함
- 보통 이 잼에서 가장 인기 있는 언어는 Fennel이며, Fennel은 Lua로 컴파일되는 Lisp임
- S7을 사용한 게임 3개도 Lisp를 게임 개발에 쓰는 방식과 연결되는 사례가 됨
Lisp를 아이싱으로 쓰는 방식
- 아이싱 패턴은 C, Rust 같은 정적 언어로 만든 “케이크” 위에 Lisp를 스크립팅 언어처럼 얹는 접근임
- 보통 더 큰 프로그램 안에 Lisp 인터프리터를 내장함
- 애플리케이션의 고수준 부분을 Lisp로 쓰고 싶다면 가장 빠른 경로가 될 수 있음
- 적절한 인터프리터나 컴파일러가 필요함
- 애플리케이션에 필요한 훅을 추가할 방법도 있어야 함
- 프로그램의 주된 부분이 C나 Rust로 작성돼 있으면 emscripten으로 WebAssembly에 컴파일해 웹에 배포할 수 있음
- 빠르게 만족할 만한 결과를 얻을 수 있지만, 정적 언어와 해당 툴체인에 강하게 결합됨
- 대표적인 예시는 다음과 같음
- S7은 내장 가능한 Scheme임
- Guile도 C 프로그램 확장에 쓸 수 있지만, 실행 파일 안에 인터프리터를 넣기보다는 보통
libguile에 동적 링크함 - Fennel은 Lua 확장 지점을 가진 기존 애플리케이션을 활용해 Lisp 같은 언어를 Lua로 컴파일함
Lisp를 케이크로 쓰는 방식
- 케이크 패턴은 가능한 한 많은 소프트웨어 스택을 Lisp로 구현하는 접근임
- Lisp를 비-Lisp 프로그램 안에 넣는 대신, 프로그램 대부분을 Lisp로 작성함
- 필요하면 외부 함수 인터페이스(FFI)로 공유 라이브러리를 호출하지만, 그 사용은 최소화하는 편이 좋음
- 결과가 나오기까지는 시간이 더 걸림
- 선택한 Lisp 구현체에 없는 라이브러리를 직접 구현해야 함
- 피할 수 없는 C 공유 라이브러리에는 래퍼를 작성해야 함
- 프로젝트가 쉽게 emscripten 대상이 되지 않아 웹 배포가 더 어려워짐
- 이 접근은 고전적인 embed vs. extend 논쟁과 맞닿아 있음
- Guile은 아이싱에도 쓸 수 있지만, 케이크로 쓸 때 더 강점을 보임
- Guile의 초기 비전은 Scheme 인터프리터를 추가해 다른 프로그램을 Emacs처럼 만드는 것이었음
- 현재의 좋은 관행은 처음부터 프로그램을 Scheme으로 작성하는 것임
- Common Lisp도 케이크 접근의 좋은 사례임
- SBCL 같은 구현체는 좋은 C FFI를 제공함
- 효율적인 네이티브 실행 파일로 컴파일할 수 있어 성능 때문에 C를 쓰고 싶어지는 상황을 줄임
Game Jam 사례로 본 아이싱과 케이크
-
Fennel + love2d
- love2d는 오랫동안 1인 또는 소규모 팀 게임 개발에 인기 있는 선택지였음
- love2d는 Lua 인터프리터를 내장한 C++ 프로그램이라 Fennel의 좋은 대상임
- 대부분의 Linux 배포판이 love2d를 패키징하므로
.love파일을 네이티브로 실행하기 쉬움 - emscripten 덕분에 love2d 게임을 웹에도 배포할 수 있음
- 그래서 대부분의 Fennel 게임은 love2d를 사용함
- ./soko.bin과 Gnomic Vengeance가 이 스택을 사용함
- Fennel+love2d는 Lisp as icing의 완전한 예시임
- Fennel은 스택 최상단에 있고, Lisp를 아래 계층으로 퍼뜨릴 경로는 사실상 없음
- 현재까지 가장 성공적인 Lisp 게임 개발 스택임
-
S7 + raylib
- 이번 잼에서는 GhostHop과 Life Predictor 두 게임이 S7+raylib 스택을 사용함
- Raylib은 여러 고수준 언어 바인딩을 가진 C 라이브러리이며 최근 몇 년 사이 인기가 커짐
- S7도 C로 구현돼 있고 쉽게 내장할 수 있어, 이 조합은 emscripten으로 웹에 배포하기 쉬움
- S7+raylib도 Lisp as icing의 사례이며, 향후 잼에서 더 인기를 얻을지 지켜볼 만함
-
Guile + Chickadee
- Chickadee는 Guile용 게임 라이브러리이며, 렌더링을 포함한 흥미로운 부분 거의 전부를 Scheme으로 구현함
- 최근 잼에서는 Turbo Racer 3000과 Bloatrunner 두 게임이 Chickadee로 만들어짐
- Guile+Chickadee는 Lisp as cake의 사례임
- Chickadee는 이미지, 오디오, 폰트 로딩 같은 저수준 작업을 위해 일부 C 라이브러리를 감싸지만, 자체 코드는 순수 Scheme으로 작성됨
- 행렬과 벡터 수학도 모두 Scheme으로 구현돼 있음
- love2d와 raylib에 견줄 렌더링 프리미티브 세트를 제공하며, 이 역시 Scheme으로 구현됨
- 다른 Lisp 게임 라이브러리가 nanosvg 같은 C 라이브러리를 쓰는 경우가 많은 반면, Chickadee는 Scheme으로 벡터 그래픽 렌더링을 구현하는 진전도 있음
- Chickadee는 Guile 컴파일러와 가상 머신의 한계를 밀어붙였고, 그 과정에서 Guile도 개선됨
- 다만 대부분 한 사람이 제한된 여가 시간에 개발해 왔기 때문에, 더 인기 있는 게임 개발 라이브러리와 기능 동등성을 갖추는 데 시간이 오래 걸림
- 현재 상태에서도 해당 목적에는 꽤 잘 동작함
-
Hoot + HTML5 canvas
- Hoot는 Scheme-to-WebAssembly 컴파일러임
- Hoot는 C로 작성된 Guile VM을 emscripten으로 Wasm에 컴파일하지 않음
- 대신 완전한 Wasm 툴체인과 Wasm을 직접 내보내는 Guile 컴파일러용 새 백엔드를 구현함
- Hoot는 전부 Scheme으로 작성됨
- emscripten으로 컴파일한 C 프로그램이 선형 메모리 기반 Wasm 1.0을 대상으로 하는 것과 달리, Hoot는 GC 관리 힙 타입을 가진 Wasm 2.0을 대상으로 함
- 이 구조 덕분에 Hoot 바이너리는 가비지 컬렉터를 포함해 배포하지 않음
- 그래서 emscripten으로 컴파일한 Lisp 런타임보다 훨씬 작음
- 한 Hoot 게임의 Wasm 바이너리는 2MiB 미만이고, 확인한 love2d 게임의
love.wasm은 거의 6MiB였음 - Hoot 프로그램은 JavaScript와 쉽게 상호운용됨
- Scheme 객체를 JavaScript로 쉽게 전달할 수 있음
- JavaScript 객체도 Scheme으로 전달할 수 있음
- 두 쪽 객체가 같은 힙에서 관리되기 때문임
- 브라우저 API는 Wasm import로 접근할 수 있어, 게임에서는 내장 HTML5 canvas API가 쉬운 2D 렌더링 선택지가 됨
- 이번 잼에서는 Hoot를 사용한 게임이 11개였고, Cirkoban과 Lambda Dungeon도 포함됨
- Hoot+HTML5 canvas는 대부분 두꺼운 케이크이고 일부 아이싱이 섞인 형태임
- Hoot 부팅에는 1년과 상당한 자금이 들었음
- emscripten을 쓰지 않고 자체 툴체인을 만들었고, Guile 컴파일러도 확장함
- Guile VM 위에서 동작하는 Wasm 인터프리터도 있음
- 반면 canvas API는 매우 고수준임
- 더 케이크에 가까운 방식은 Hoot의 JS FFI로 WebGL 또는 WebGPU를 호출하는 것임
- 향후 계획은 WebGL/WebGPU 쪽이며, 이를 가능하게 하려면 Wasm GC 개선이 필요함
- 또 다른 목표는 Chickadee를 Hoot에 포팅해 Chickadee 게임을 love2d 게임처럼 네이티브와 브라우저에서 쉽게 플레이하게 만드는 것임
케이크 접근의 한계와 장점
- 케이크 접근에도 한계는 분명함
- 현대 환경은 Lisp 머신의 세계가 아니며, 가장 높은 Lisp 케이크도 대부분 C로 된 더 큰 케이크 위에 놓임
- 현대 Lisp 시스템은 어느 지점에서는 아래 계층에 도달함
- Emacs는 C 코어 위에 있음
- Guile VM은 C로 작성됨
- Hoot는 V8 같은 C++ 기반 대형 JavaScript 엔진 위에서 실행됨
- Hoot 게임은 현재 WebGL/WebGPU가 아니라 HTML5 canvas로 렌더링함
- OpenGL 사용에는
libGL이 필요함 - Chickadee는 C FFI로
libGL을 호출하는guile-opengl을 사용함 libpng, FreeType 등도 존재함
- 모든 것을 Lisp로 다시 작성하기에는 자원 문제가 큼
- 그래도 스택의 일부를 C 같은 언어에서 되찾는 것은 작은 승리임
- Lisp로 작성된 부분은 해킹하기 더 쉽고, 일부는 프로그램 실행 중 라이브 해킹도 가능함
- GC 관리 런타임 덕분에 일반적으로 메모리 안전성을 얻을 수 있음
- FFI 호출이 줄면 Lisp/C 경계를 넘는 오버헤드가 줄고 안전성도 높아짐
- 스택에서 Lisp 비중이 커질수록 아이싱보다 케이크에 가까워짐
게임 바깥의 케이크 사례
- Guix는 케이크 접근이 얼마나 강력해질 수 있는지 보여주는 좋은 예시임
- Guix는 Nix 프로젝트의 함수형 패키징 모델을 가져와 새로 구현하면서 Nix 언어를 Guile로 대체함
- 그 이유는 코드 스테이징, 코드 공유, 해킹 가능성 향상임
- Guix는 systemd 대신 Guile로 작성한 init 시스템도 사용하며, 이 선택 역시 같은 이유에서 나옴
- Guix는 초기에는 바퀴를 이유 없이 다시 발명한다는 비판을 받기 쉬웠지만, 10년이 지난 지금 Lisp 사용을 극대화한 고집이 프로젝트 성공의 핵심이 됨
- 사용자는 Guix 관용구와 약간의 Guile을 익히면 운영체제를 원하는 대로 구성할 수 있는 강력한 능력을 얻음
- Guix는 현대 하드웨어에서 Lisp 머신에 가장 가까운 경험으로 볼 수 있음
- Common Lisp 쪽에서는 Trial 게임 엔진이 C 라이브러리를 감싸는 대신 많은 부분을 Common Lisp로 구현한 사례임
- Pre-Scheme 같은 프로젝트는 GC 관리 런타임 아래 계층도 언젠가 Lisp로 구현할 수 있다는 기대를 줌
- Pre-Scheme은 Scheme 48에서 개발되고 성공적으로 사용됨
- NLnet grant 덕분에 현대적 부활이 기대됨
Lisp로 더 많은 스택을 만들려는 방향
- 방향은 케이크 쪽에 가까움
- Lisp가 할 수 있는 일의 경계를 계속 밀어붙이는 프로젝트가 더 많아질 필요가 있음
- Lisp Game Jam에서 가장 흥미로운 것은 게임 자체보다, 낡고 건조한 C에서 케이크 한 조각을 되찾는 작은 진전임
- Guile 게임 개발에서는 Chickadee 프로젝트로 한계를 계속 밀어붙이려 함
- 결론은 Rust로 다시 쓰기가 아니라 Lisp로 다시 쓰기임
댓글과 토론
Hacker News 의견들
-
객관적으로 소프트웨어 접근법을 비교하는 글을 요즘 거의 못 봐서 더 반가웠음
그런 글을 찾아보려 해도 요즘은 검색 결과가 SEO 스팸을 넘지 못하는 경우가 많음
Janet이 게임용으로 만들어진 듯하고 웹 서버나 그래픽 같은 “배터리 포함” 요소도 의외로 많아 보이는데, Janet을 쓰는 게임이 안 보여 조금 놀랐음
Lisp와 게임 쪽에서는 한 번 살펴볼 만한 언어라고 봄 -
s7이 주목받는 게 반가움
Max/MSP 컴퓨터 음악 환경에 Scheme 인터프리터를 넣는 오픈소스 확장인 Scheme for Max에서 Scheme으로 썼고, Guile, Clojure, Common Lisp 사이 어딘가에 있으면서도 아주 작고 임베드하기 쉬움
Guile보다 훨씬 관대한 BSD 라이선스라는 점도 좋음
일급 환경을 가진 Common Lisp 매크로를 좋아한다면 s7도 마음에 들 가능성이 큼
WASM에서도 쓰기 매우 쉬웠고, 음악 교육 프로젝트에서 그렇게 쓰고 있음
Scheme에서 JS 함수를 호출하고 반대로 JS에서 Scheme을 호출하는 범용 함수를 만드는 것도 어렵지 않아 전체 흐름이 매끄러웠음- s7에 한 표 더함
iOS와 Android의 네이티브 앱에서 그래픽을 제외한 엔진으로 s7과 SQLite를 성공적으로 임베드했음
매우 빠르고, FFI가 좋고, 안정적이며 작았고, 모바일 앱 간 공유 코드와 엄청 빠른 단위 테스트, 깔끔한 도구 체계에서 큰 이득을 봤음
결국 모바일에서는 더 실용적인 Lisp인 Fennel로 옮겼음
모바일에서 s7을 쓰는 팀은 거의 우리뿐이었던 반면 Lua는 모바일 확장 언어로 훨씬 흔했고, r7rs Scheme 호환성 상태도 영향을 줬음
데스크톱 개발에는 Guile을 쓰고 배포는 s7로 하다 보니 미묘한 비호환성, 예를 들면 매개변수 평가 순서 같은 문제가 자주 걸렸음
s7과 Fennel 모두 훌륭한 프로젝트와 커뮤니티를 갖고 있음
- s7에 한 표 더함
-
Spritely Institute를 특히 블로그까지 살펴보길 권함
무엇을 하는 곳인지는 스포일러하고 싶지 않지만, 깊이 파볼 만한 주제와 기관임
블로그와 관련 링크, 프로젝트를 읽는 데만 10시간 넘게 썼음
https://spritely.institute/archive/- 좋은 사람들을 끌어들이고 꽤 멋진 일을 하는 곳으로 보임
-
“우리는 Lisp 머신의 세계가 아니라, 미화된 PDP-11의 세계에 살고 있다”는 요약이 인상적임
그런데 sdl과 함께 쓰는 “아이싱”도 있는지 궁금함- 전통적인 CPU를 미화된 PDP-11이라고 하긴 어려움
태그 없는 메모리를 쓰는 폰 노이만 기계라는 점에서는 비슷하지만, 32비트 마이크로프로세서가 보편화되고 Lisp 컴파일러 기술이 그에 맞게 발전한 80년대 중반쯤부터 전통적 CPU가 Lisp 머신을 앞지르기 시작했음
“태그 없는 메모리”라는 부분도 CHERI 같은 흐름을 보면 계속 유지되지 않을 수 있고, LispM 스타일 아키텍처가 크게 돌아올 여지가 있음 - PDP-11을 정말 좋아했음
몇 년 동안 거실에 PDP-11/45를 두고 있다가, 나중에는 공간을 줄이려고 8인치 플로피 두 대가 달린 H-11 LSI-11/2 몇 대로 바꿨음
PDP-11은 Unix의 원형질 같은 존재일 뿐 아니라 개념적으로 잘 설계됐음
우리가 “한 화면에 들어가는” 루틴을 선호하듯, PDP-11의 작은 직접 주소 공간은 너무 크지 않은 모듈을 유도하고 모듈성을 장려함
오늘날에는 부족한가? 물론이고, 특히 빅데이터에는 고통스러움
그래도 PDP-11이 성공했고 지금도 흔적으로 남아 있는 데에는 개념적인 이유가 있음 - 아이러니하게도 이제는 하드웨어 메모리 태깅을 갖춘 C 머신으로 가고 있음
C를 고칠 다른 방법이 없고, 다시 쓰이지 않을 코드가 너무 많기 때문임 - 끝부분이 더 좋았음: “Rust로 다시 쓰라고? 절대 아님! Lisp로 다시 써라!”
- Lisp는 메모리 계층 구조 때문에 현대 CPU에 적합하지 않음
Lisp는 주로 리스트를 다루고, 리스트는 메모리 곳곳의 포인터를 따라갈 수 있음
예전 CPU에서는 메모리가 대체로 같은 임의 접근 시간을 가져 문제가 아니었지만, 현대 CPU는 메모리 포인터를 같은 속도로 따라갈 수 없고 성능을 위해 지역성 규칙을 따라야 함
그래서 C나 Fortran 배열 같은 것을 쓰는 알고리즘이 Lisp 리스트 기반 버전보다 항상 빠를 수밖에 없음
- 전통적인 CPU를 미화된 PDP-11이라고 하긴 어려움
-
최근 Guile Scheme의 진전이 기대됨
마지막으로 봤을 때와 달리 인터프리터 언어에서 제대로 된 컴파일러를 갖춘 언어가 됐고, 이제 Hoot로 WASM 컴파일도 가능해졌음
Clojure, uLisp, Common Lisp에는 익숙하지만 Guile Scheme은 Common Lisp의 군더더기를 많이 덜어낸 느낌이고, 특히 Guix와 Shepard가 자리 잡는다면 컴파일되는 Lisp를 손에 두고 싶음
Little Lisper와 SICP 말고 Guile Scheme을 효과적으로 배우는 좋은 자료가 있는지 궁금함 -
앞으로 만들 것에 Guile을 써볼 생각을 하고 있어서 재미있게 읽었음
WASM 쪽 작업도 잘된 것 같음- 말장난을 좋아한다면 Andy Wingo의 이 글을 읽기 전에 의자에 단단히 앉는 게 좋음
https://wingolog.org/archives/2024/05/16/on-hoot-on-boot
- 말장난을 좋아한다면 Andy Wingo의 이 글을 읽기 전에 의자에 단단히 앉는 게 좋음
-
최근 Clojure로 3D 보스전 프로토타입을 만들었음: https://prototype-game.pages.dev
- 훌륭함
예전에는 모든 형태의 웹 개발을 싫어했는데 ClojureScript는 실제로 즐겁게 만들어 줬고, 더 널리 쓰였으면 함 - 모바일에서는 조작이 안 됨
나중에 PC 앞에 있을 때 다시 시도해보겠음 - 멋짐
어떤 라이브러리를 쓰고 있는지 궁금함 - 만든 것에 대한 애정을 담아 말하자면, Avatar - Legends of the Arena가 떠오름
어린 시절에 정말 좋아했던 게임 중 하나였음
https://www.youtube.com/watch?v=dcJFldES9dg
- 훌륭함
-
표에 넣어 달라고 요청하긴 했지만, 이제는 오래된 소식일지도 모르겠음
참고:
https://lispy-gopher-show.itch.io/logos-lisp-legend/devlog/7...
https://itch.io/post/10013482 -
https://ianthehenry.com/posts/janet-game/가 있는데도 Janet이 빠졌음
글의 저작권 연도가 1899~1907년으로 되어 있지만, 빈티지처럼 보이지 않는 점은 아쉬움- Janet이 빠진 것 같음
스크립팅에는 다시 Fennel로 돌아갔는데, 훨씬 많은 Lua 라이브러리를 바로 가져다 쓸 수 있고 거의 어디서나, 심지어 iPad의 a-Shell에서도 문제없이 실행할 수 있기 때문임 - Janet을 아주 진지하게 검토했지만, 웹 앱과 WebGL(ThreeJS)을 쓰려는 목표까지 바로 가는 길이 보이지 않았음
이 스레드에 유용한 참고 자료가 있었고 나중에 다시 시도해볼 수 있음: https://janet.zulipchat.com/#narrow/stream/409517-help/topic...
이미 검증된 프로덕션 경로가 있었는데도 제시간에 겨우 플레이 가능한 무언가를 만들었고, “플레이 가능”이라는 표현도 좀 후한 편임
- Janet이 빠진 것 같음
-
Emacs Lisp로 어떤 게임을 만들었는지 매우 궁금함
게임 프로그래밍을 떠올릴 때 가장 먼저 생각나는 선택지는 아님- GNU Emacs에는 텍스트 어드벤처 Dunnet이 기본으로 포함됨
Dunnet은 원래 Ron Schnell이 1982년에 TOPS-20에서 동작하는 Maclisp 프로그램으로 작성했음
1992년에 Emacs Lisp로 포팅했지만, 단순 포팅을 넘어서 새 방, 아이템, 퍼즐을 추가했고 MIT 중심 콘텐츠는 제거했음
예를 들어 게임 끝부분의 “endgame” 컴퓨터는 원래 MIT-SALLY라는 이름으로 MIT에 있었고 Chaosnet으로 접근했지만, GNU Emacs 버전에서는 이런 낡은 MIT 참조를 없앴음
대신 VAX 11/780처럼 더 널리 알아볼 수 있지만 역시 시대감 있는 내용은 의도적으로 들어 있음
https://en.wikipedia.org/wiki/Dunnet_(video_game)
원본은 여기 있음: https://github.com/Quogic/DunnetPredecessor/blob/master/foo.... - https://bcardoso.itch.io/shoggy
https://lcolonq.itch.io/slgj2024-game-boy-gizmo
https://asquared31415.itch.io/disassembly
https://grindingstone.itch.io/pendulum - 기본 설치에 snake와 tetris가 들어 있음
- Malyon은 Z-machine 인터프리터이고 대체로 잘 동작함
- GNU Emacs에는 텍스트 어드벤처 Dunnet이 기본으로 포함됨