2P by GN⁺ 19시간전 | ★ favorite | 댓글 2개
  • JavaScript 문법은 중첩된 괄호와 콜백으로 쉽게 복잡해지고, 작은 UI에도 많은 라이브러리를 끌어오는 비대화가 생김
  • WebAssembly는 브라우저에서 다른 언어를 실행할 길을 열지만, Pyodide처럼 JavaScript 이벤트 루프와의 비동기 연결 비용이 큼
  • 브라우저 자원과 WebAssembly 메모리는 제한적이어서, JavaScript를 대체하려 하기보다 협상하는 접근이 필요함
  • LispE는 3.3MB WASM 바이너리 하나에 450개 이상 함수를 담고, 문자열·계산·행렬·정규표현식을 함께 제공함
  • evaljsasyncjs로 JavaScript 함수와 DOM을 활용하면서, 여러 외부 라이브러리 대신 감사 가능한 단일 바이너리로 코드 비대화를 줄임

JavaScript 비대화와 브라우저 제약

  • JavaScript 문법은 괄호, 중괄호, 대괄호가 겹치고 닫는 순서를 맞춰야 해서 코드가 쉽게 복잡해짐
    • forEach 콜백 안에서 여러 객체에 값을 넣고 조건부 캐시를 갱신하는 예시가 나옴
newNames.forEach((name, i) => {
    allAgentContents[name] = contents[i];
    agentModes[name] = modes[i];
    if (compiled[i]) agentCompiledCache[name] = compiled[i];
    agentViewingCompiled[name] = viewing[i];
});
  • 기본 프로그래밍 언어라면 흔히 포함하는 처리를 위해 JavaScript에서는 많은 라이브러리를 내려받게 됨
    • C나 C++도 작업을 하려면 include가 필요하지만, JavaScript 라이브러리는 누가 구현했고 누가 유지보수하는지 알기 어려운 경우가 많음
    • 작은 창에 _hello_를 표시하려고 “인터넷의 절반”을 로드하는 듯한 페이지도 생김
  • 인터넷은 JavaScript에 의존하며, TypeScript로 감추더라도 브라우저에서 페이지를 열 때마다 거쳐야 하는 문지기로 남아 있음
  • 브라우저가 궁극의 운영체제가 되어 Windows나 Mac OS를 불필요하게 만들 것이라는 꿈이 있었고, 그 꿈을 구현할 언어로 JavaScript가 선택됨

WebAssembly와 JavaScript의 관계

  • WebAssembly는 자체 기계어를 가진 가상 머신에 가까워, 브라우저 안에서 다른 방식으로 코딩할 가능성을 열어줌
  • Pyodide는 브라우저에서 Python을 실행하는 인상적인 엔지니어링 성과지만, JavaScript 영역 안에서 움직이는 비용도 드러냄
    • Python의 asyncio와 JavaScript 이벤트 루프라는 두 비동기 세계가 서로 대화해야 함
    • 두 세계 사이의 다리는 취약하며, 모든 await가 어느 세계에 속하는지 기억해야 함
  • 브라우저 자원은 제한적이어서, 초기 컴퓨터 과학 시절처럼 매 명령과 구조를 비트 단위로 살펴보는 사고방식이 필요함
    • 1981년에 정확히 15772 bytes만 남은 컴퓨터에서 프로그래밍을 시작했다는 기준이 제시됨
    • 현대 브라우저 메모리가 그 첫 컴퓨터만큼 제한적인 것은 아니지만, WebAssembly는 일반 프로그램처럼 메모리를 자유롭게 소유하지 못하고 제한된 방식으로 먼저 허락을 받아야 함
  • 핵심 태도는 JavaScript와 싸우는 것이 아니라 협상하는 것임

LispE가 제시하는 대안

  • LispE는 단일 바이너리 안에 450개 이상의 함수를 제공함
    • WASM 바이너리 크기는 3.3 MB
    • 문자열, 계산, 행렬, 정규표현식 처리 기능이 한곳에 묶여 있음
    • WASM 바이너리는 binaries/wasm에 있음
  • 작은 인터프리터지만 반환값으로 문자열, Float64Array, 정수, 실수, 문자열 배열을 다룰 수 있음
  • LispE는 Lisp 기반이라 AST가 살아 있는 구조를 가짐
  • Lisp 문법이 맞지 않으면 Python이나 Basic과 구분하기 어려운 언어를 위한 트랜스파일 문법을 사용할 수 있음
    • grammar를 수정해 원하는 스타일의 언어를 만들 수 있음
    • 그리스어로도 가능하며, 이미 그리스어 예시가 있음
  • LispE는 JavaScript와 싸우지 않고 JavaScript 함수와 DOM 기능을 활용하는 방식으로 협력함

JavaScript 호출: evaljs와 asyncjs

  • LispE 안에서 JavaScript 코드를 실행하려면 evaljsasyncjs 를 사용할 수 있음
    • evaljs는 JavaScript 코드를 실행해 값을 받음
    • asyncjs는 페이지에 정의된 사용자 JavaScript 함수와 비동기 콜백을 연결함
(setq a (evaljs "10 + 20 + 30")) ; execute some JS code

; call_llm is a user-defined JS function in the page
(asyncjs `call_llm("Implement a piece of code in Python to sort strings");` 'mycallback)

(defun mycallback(theresult) ...)
  • asyncjs는 작업이 끝나면 돌아오겠다는 Promise로 동작함

코드 비대화 줄이기

  • 1995년 Wirth 인용은 더 빠른 컴퓨터와 더 큰 모델이 문제를 해결하지 않으며, 코드를 날씬하게 유지해야 한다는 결론으로 이어짐
  • LispE는 브라우저에 노출되는 450개 함수를 단일 감사 가능 바이너리로 제공함
    • 각 기능을 따로 구현하려면 수치 계산용 mathjs, 컬렉션용 lodash, 문자열 조작용 voca, 통계 분포용 simple-statistics 같은 라이브러리를 로드해야 함
    • 이런 방식은 각자 다른 작성자, 버그, 유지보수 일정을 가진 수백 MB의 코드로 커질 수 있음
  • LispE는 하나의 유지보수되는 코드로 이 기능들을 제공하며, 오픈소스라 전체 코드 감사를 할 수 있음
  • Garbage Collector가 최악의 순간에 코드 성능을 무너뜨리지 않는다고 봄
  • JavaScript와는 단순 API 호출로 투명하게 통신하며, 미리 정의된 구조를 반환할 수 있음
// floats here is a Float64Array
const floats = callEvalLispEToFloats(0, `(normal_distribution 100)`);

댓글과 토론

글이 뭔가 뜬금없어서 출처를 의심했는데 네이버였다니 ㄷㄷ
근데 역시 반응은 안 좋네요... 솔직히 자바스크립트가 과장돼서 3.3MB짜리 WASM까지 올려서 리습을 쓰겠다는 게 쉽게 이해되지 않는 오버 엔지니어링이죠 ㅋㅋㅋ

네이버 계정인 이유에 대해 Claudius라는 프로젝트 개발자가 댓글을 남겼는데 본인이 Naver Labs Europe에서 일하고 있고 네이버가 오픈소스 프로젝트로 승인해서 올라갔다고 하네요.
네이버랑 크게 상관은 없는 거 같고 그냥 리습을 진짜 사랑하는 분들인듯...

Lobste.rs 의견들
  • JavaScript를 덜 부풀리겠다면서 불투명한 3.3MB WASM 덩어리를 추가하고 앱은 Lisp로 쓰라는 건가 싶음
    순수 JavaScript와 추가 의존성 0개만으로도 그 크기의 10분의 1에 닿기 전까지 꽤 많은 걸 만들 수 있음

    • 문서의 작은 한 부분만 보고 dismiss하지는 않았으면 함
      이 활용 방식에는 공감하지 않지만, 요즘 보기 드문 기묘함이 있는 재미있는 Lisp 열정 프로젝트이고, 모든 문서가 사람 손으로 쓴 티가 나는 독특한 문장이라 좋음
  • 표준 라이브러리에 새로 넣을 만한 건 언제나 제안받고 있음
    최근 추가된 것으로는 집합 합집합/교집합, sum, base64 등이 있음
    다만 미적분 함수 요청은 거의 못 들었고, 문자열 관련으로 꾸준히 요청되는 건 .reverse.titleCase 정도임
    .reverse는 장난감 문제 말고 설득력 있는 쓰임새가 많지 않고, .titleCase가능은 하지만 국제화 데이터가 필요해서 논의가 진행 중임
    게다가 둘 다 이 라이브러리에는 없음

    • 많은 사람이 tc39 구성원을 직접 알지 못하거나, 그들이 우리 주변에 있다는 걸 모르는 듯함
      제안을 해도 실제 반영까지 3년 걸릴 테니 시도할 가치가 없다고 느끼고, utils/ 폴더는 별로지만 당장 만들 수 있다고 생각하는 것도 있음
    • 표준 라이브러리가 내가 추적한 것보다 많이 움직였으니 글을 조금 고쳐야겠음
      내 주장은 JavaScript에 함수가 부족하다는 것보다는 배포 모델에 더 가까움
      표준 라이브러리가 꽉 차 있어도 평균적인 앱은 여전히 전이적 npm 의존성을 메가바이트 단위로 싣고 나감
      LispE-as-WASM은 3.3MB, 문서화된 함수 약 450개, C++ 코어, GitHub 공개 소스로, 감사 가능한 런타임이 어떤 모습일 수 있는지 보는 실험임
      실제로 그 위에 에이전트 하네스를 만들었고, LispE 규칙을 브라우저 안에서 즉석 실행하고 있음
      JavaScript 이벤트 루프는 아주 좋아함
  • 누군가 문법을 부풀었다고 표현하면 긴장하게 됨
    최소 문법은 시각적 구분을 위해 서로 다른 문자를 쓰는 문법보다 훨씬 읽기 어려울 수 있음
    LispE가 대안으로 내놓는 걸 보면 이렇다:

    (defun gol8(⍵) ((λ(⍺) (| (& (== ⍺ 4) r) (== ⍺ 3))) (⌿ '+ (⌿  '+ (° (λ (x ⍺) (⊖ ⍺ x)) '(1 0 -1) (↑ (λ (x) (⌽ ⍵ x)) '(1 0 -1)))))))  
    

    ... !?
    https://github.com/naver/lispe/wiki/5.3-A-la-APL

  • 이 프로젝트로 들어가는 진입점이 좀 이상하고, https://github.com/naver/lispe/wiki/1.-Introduction 쪽이 더 나아 보임
    WASM을 대상으로도 제공하는 네이티브 Lisp 프로젝트이고, 프로그래밍 언어 덕후들이 좋아할 만한 재미있는 요소가 있음

  • 첫인상은 꽤 억지스러운 JavaScript 예제라는 느낌이었음
    JavaScript 혐오의 상당 부분은 초기 브라우저 비호환성, 고통스러운 DOM API, 문법상의 함정에서 나온 것 같고, 이런 것들은 요즘 내 일상에는 사실상 영향이 없음
    현대 JavaScript, 특히 TypeScript와 합리적인 린트 규칙을 쓰면 언어로서는 충분히 괜찮음
    여전히 CJS 대 ESM, 공급망 위험, 생태계 변동 같은 문제는 있지만, 대부분은 언어 자체 바깥에 있음

  • 괄호 균형 맞추기가 너무 성가셔서 사람들이 편집기 확장까지 만든 바로 그 경험이 훌륭하다는 건가
    이런 스타일의 프로그래밍, 즉 낮은 문법 밀도와 함수형 방식을 원한다면 방향을 뒤집어서 연결형(concatenative) 언어로 가는 편이 나음

  • Lisp 해석기가 컴파일된 결과물로 어떻게 3.3MB까지 갈 수 있는지 궁금함
    이게 덜 부풀리는 건가?

    • 표준 라이브러리가 포함돼 있어서 그럴 가능성이 큼
      그렇다 해도 쓰지 않는 코드를 포함한 큰 표준 라이브러리를 함께 배포하는 건 덜 부풀리는 것과 거리가 멀다는 데 동의함
  • 충격! 공포!
    닫는 괄호를 올바른 순서로 닫아야 한다니 놀라움

    • 올바른 순서로 닫지 않아도 되는 언어를 원한다면 J가 있음
      JavaScript 같은 언어의 장황함에 질색하는 Lisp 애호가들도 APL 계열 언어 앞에서는 돌아서서, 최대한 짧은 것보다 명확성가독성이 훨씬 중요하다고 논쟁하기 시작하곤 함
      LispE 작성자는 APL 기본 연산에 어느 정도 애정이 있어 보이지만, 그렇게 간결한 표기법의 언어를 접하고도 Conway의 _Life_를 APL, J, K, 심지어 Q 버전보다 길고 성긴 깊은 중첩 s-표현식 버전으로 선호하는 이유는 이해하기 어려움