2P by GN⁺ | ★ favorite | 댓글 1개
  • 20년간 Ruby on Rails 프리랜서로 일한 경험은 Common Lisp 프로젝트로 이어졌지만, 성능·이식성·실행 환경 한계가 누적되며 다시 C를 선택하게 됨
  • cl-facts는 빠른 트리플 저장소와 중첩 가능한 원자적 트랜잭션을 갖췄지만, 개발 시간이 길어지며 클라이언트를 잃는 결과도 낳음
  • 가상머신, Linux cgroups 기반 컨테이너, 가비지 컬렉터에 대한 불만은 C가 여전히 시스템 소프트웨어의 현실적 기반이라는 판단으로 이어짐
  • libc3에서 출발한 작업은 C3 언어, 인터프리터 ic3, 컴파일러 c3c 구상으로 확장됐고, 이름 충돌 때문에 이후 KC3로 바뀜
  • 현재 KC3는 C89로 포팅한 그래프 데이터베이스, REPL ikc3, MVC 웹서버 kc3_httpd, Markdown-to-HTML C 구현 기반 문서 사이트까지 포함함

Common Lisp 작업이 C 재작성으로 이어진 과정

  • 5년간 프랑스 컴퓨터 학교에서 공부하고 20년간 Ruby on Rails 프리랜서 개발자로 일한 뒤, 짧은 학습으로 생각했던 Common Lisp가 점점 더 큰 프로젝트가 됨
  • Common Lisp로 C 코드를 생성해 ASN.1 파서와 쿼리 시스템을 만들었고, 이 작업은 커스텀 Common Lisp-to-C SNMP 서버로 확장됨
  • 이후 여러 Common Lisp 패키지를 작성함
    • cl-unix-cybernetics는 GitHub 저장소 중 가장 많은 별을 받은 프로젝트가 됨
    • cl-streams, cffi-posix도 작성함
    • cl-facts는 Common Lisp 그래프 데이터베이스로 쓸 수 있는 트리플 저장소임
  • cl-facts는 빠른 성능, 원자적 트랜잭션, 중첩 가능한 트랜잭션, unwind-protect 호환성, 3개 매크로만 배우면 되는 사용성을 갖춘 결과물임
  • cl-facts는 벨기에의 European Lisp Symposium에서 라이트닝 토크로 발표됐고, 발표 슬라이드는 facts.pdf에 있음
  • Common Lisp 패키지 개발에 시간이 오래 걸리며 클라이언트를 잃었지만, Common Lisp는 미래 세대를 위한 도구라고 판단함

C, KC3, 그리고 현재 구성

  • 가상머신은 CPU와 대역폭을 에뮬레이션에 낭비하고, Linux cgroups 기반 컨테이너에서는 RCE와 권한 상승 문제가 계속 발견된다는 경험이 OpenBSD 중심 선택으로 이어짐
  • Terraform, Ansible 같은 DevOps 도구를 피했고, VM과 컨테이너뿐 아니라 프로그래밍 언어 자체에 불만을 가진 사람들도 봄
  • Clojure로 수천 유닛이 각자 세계 인식을 갖는 전략 게임을 만들려던 사례는 가비지 컬렉터 때문에 실패함
  • Common Lisp 프로젝트들도 가비지 컬렉터 때문에 적용 범위가 제한됐고, JVM의 가비지 컬렉터는 잘 만들기 위해 큰 비용이 드는 상용적 강점으로 평가됨
  • 성능과 이식성을 고려하면 별도 도구가 없는 한 합리적 선택은 C라고 판단함
    • Linux는 C로 작성됨
    • OpenBSD는 C로 작성됨
    • GTK+는 객체지향 순수 C로 작성됨
    • GNOME은 C로 작성됨
    • Linux 데스크톱 앱 다수도 오래된 C로 작성됨
  • libc3 유틸리티 라이브러리에서 출발해 C3 언어, 인터프리터 ic3, 컴파일러 c3c 구상으로 발전함
  • UTF-8 버퍼와 자료구조가 빠르게 오가도록 만들었고, 메모리 비용을 감수해 bounds-check를 적용함
  • 방어적 프로그래밍을 전면에 두고 버그를 처음부터 0으로 줄이는 방향을 잡았으며, KC3 코드는 보안 함의 없이 실행된다고 밝힘
  • 초기 인터프리터는 언어의 모든 자료형을 담는 enum-tagged union인 tags를 REPL에서 처리하는 형태로 만들어짐
  • 3년 뒤 5계층 리팩터링을 끝냈고, 테스트가 다시 모두 통과했으며 웹서버도 다시 깨지지 않는 상태가 됨
  • 언어 이름은 C3가 이미 사용 중이어서 KC3로 바뀜
  • 기존 Common Lisp 그래프 데이터베이스 cl-facts는 C89로 포팅됨
    • 대부분은 2020년 Covid-19 봉쇄 기간에 작성됨
    • 트리플 추가·삭제, 재귀 쿼리 시스템, 트랜잭션, 로깅, 영속성을 포함함
    • Common Lisp 원 설계를 C89로 거의 그대로 구현함
  • KC3에는 여러 알고리듬 자료형의 형식적 의미를 다루기 위한 파서와 생성기도 포함됨
    • Structs, Linked lists, Maps, Hash tables, Time, Complex, Rationals, Tuples, Code blocks, Quotes, Unquotes, Copy on write, Skip lists, Sets 등이 포함됨
    • 매크로가 있으며, 후속 글에서 예제를 다룰 예정임
    • José Valim과 Elixir 작업에서 큰 영감을 받음
  • ikc3 REPL은 키보드나 파일 입력을 파싱하고 KC3 평가 결과를 표준 출력으로 내보내며, KC3 단위 테스트 2단계 대부분에 사용됨
  • kc3_httpd는 MVC 프레임워크를 갖춘 웹서버이며, 현재 웹페이지를 생성함
  • Common Lisp 글 조회 수가 700회를 기록했고, 문서 웹사이트는 kc3_httpd와 확장된 Markdown-to-HTML C 구현으로 작성됨

댓글과 토론

Hacker News 의견들
  • 오히려 반대쪽에 가까움. 10대 때 VB를 많이 했고, 대학에서 Java, C, C++를 배운 뒤 개인·업무 프로젝트는 주로 C로 정착했으며 Xfce 핵심 개발자로 5년 일했음
    이후 백엔드로 옮겨 Java, Scala, Python을 썼는데, 감히 말하면 쉬웠음. 표준 라이브러리가 풍부하고, 의존성을 자동으로 가져오는 빌드 시스템이 있고, 필요한 거의 모든 것에 오픈소스 라이브러리가 있는 거대한 커뮤니티가 좋았음
    12년 뒤 다시 Xfce에 돌아와 보니 C는 너무 고통스러움. 라이브러리가 있어도 사용자들이 쓰는 여러 배포판·OS에 패키징되어 있지 않아 바퀴를 계속 재발명하게 되고, 메모리 누수, NULL 포인터 역참조, 해제 후 사용, 데이터 경쟁, 형편없는 동시성 기본 요소, 튜플·제네릭 부재, 원시적인 타입 시스템까지 싫어짐
    다른 프로젝트에서 Rust를 쓰고 있는데, 배우고 쓰기는 객관적으로 더 어렵지만 C보다 Rust에서 훨씬 생산적임

    • Rust는 배우기는 더 어렵지만 일단 체득하면 쓰기, 적어도 올바르게 쓰기는 더 어렵지 않다고 봄. C는 표준 도구가 -Wall 이상의 도움을 거의 주지 않아서 올바른 코드를 쓰기 어려움
      Rust의 일반 오류 메시지는 매우 친절함. 예를 들어 String이 필요한 곳과 &str이 필요한 곳을 잘못 쓰면 빌리기를 고려하라는 식으로 고쳐 쓸 코드를 제안해 줌
      게다가 편집기의 rust-analyzer가 빌드하기도 전에 오류를 잡아줘서, 그 정도 오류를 보려면 일부러 우회해야 했음
      cargo clippy를 자주 돌리는 습관도 강력 추천함. 관용적이지 않은 코드를 잡아주는 훌륭한 도구이고, 제안들을 보며 많이 배웠음
    • C의 가장 큰 문제는 필요한 기능과 추상화를 만들 수 있게 도와줄 언어 기능 자체가 부족하다는 점임
      C++는 낮은 수준의 비트 조작부터 자동 메모리 관리나 고수준 데이터 객체까지 어떤 수준의 추상화도 만들 수 있을 만큼 기능을 제공함
      C에서는 낮은 수준의 세부사항에서 절대 벗어날 수 없고, 기어 다니도록 저주받은 느낌임
    • Memory leaks, NULL pointer dereferences, use-after-free 같은 것들을 여러 해 겪었고, 결국 그냥 안 하도록 배웠음. 발바닥에 모래알이 박혔는데 피부가 굳은살로 감싸버린 느낌에 가까움
    • Zig를 봤는지 궁금함. Rust가 현대적인 C++라면 Zig는 현대적인 C라고 자주 불리고, 잘 맞을 것 같음
  • 그 감정은 완전히 이해됨. 몇 년 전부터 순수 C로 뭔가 만들고 싶은 강한 충동이 있었음. 주 언어는 C++지만, 오래된 C 라이브러리의 인터페이스가 단순하고 기본적이라 정말 즐겁다는 걸 반복해서 느낌
    순수 C로 메서드를 만들 때는 C++나 Rust의 복잡성 때문에 생기는 아키텍처 결정보다 알고리즘 측면에 100% 집중할 수 있어 좋음. C는 강력하면서도 언어 기능 전체를 머릿속에 어렵지 않게 담을 만큼 단순해서 매력적임
    C가 직접 하도록 강제하는 점도 좋음. 마법과 복잡성을 숨기지 않음. 표준 자료구조를 직접 작성하면 더 많이 배우고, 라이브러리 추상화 여러 층 아래 숨어 있었을 특정 사용 사례의 성능 개선 가능성도 빨리 보임
    그래서 주변은 최신 C++ 기능을 쓰려 하고, 나는 점점 C++ 기능을 덜어내려는 이상한 상황이 됨. 예를 들어 문자열 복사를 피하려고 std::string_view로 복잡한 구성을 하는데, 같은 기능을 단순한 const char* 포인터와 더 적은 코드로도 만들 수 있었던 경우를 여러 번 봄

    • 16년쯤 전 “C처럼 쓰는 C++”를 쓰는 기술 회사와 일했음. C++ 컴파일러를 쓰지만 거의 모든 코드를 C처럼 작성했고, 예외적으로 클래스는 다형성·상속 없이 합성만 쓰는 Python 데이터 클래스처럼 사용했음
      클래스는 숨기기보다 캡슐화하기 위한 것이었고, 시간이 지나며 람다 같은 일부 C++ 기능은 허용됐지만 전반적으로 데이터 클래스가 붙은 C를 썼음. 매우 빨랐고, C식 malloc으로 직접 메모리를 관리했으며, 메모리가 무엇을 하는지 아는 것이 최적화에 크게 도움이 됨
      가능한 한 데이터와 코드가 캐시에 올라가도록 목표를 잡았고, 결과는 시장 선도 수준이었음. 그 회사의 얼굴 인식은 매년 NIST FR Vendor 테스트에서 계속 상위 5개 알고리즘에 듦
    • C에 가비지 컬렉터를 붙여 쓰면 꽤 해방감이 있음. malloc() 대신 GC_malloc()을 쓰고 해제하지 않으며, 링크할 때 -lgc를 추가하면 됨
      요즘 대부분 시스템에 이미 있고 많은 것들이 사용함. 정말 확실한 경우 GC_free()로 효율을 조금 올릴 수 있지만 선택 사항이고 위험도 커짐
      객체 안에 포인터가 전혀 없다는 걸 확실히 알면 문자열, 버퍼, 이미지 같은 큰 객체에 GC_malloc_atomic()을 써서 효율을 높일 수 있음
      약한 포인터도 있고, 객체가 GC될 때 파일이나 네트워크 연결을 닫아야 하는 드문 경우에는 종료자도 추가할 수 있음. 단순히 malloc()GC_malloc()으로 바꾸는 것만으로도 꽤 멀리 감
      Boehm GC를 완전히 투명한 malloc() 대체재로 만들 수도 있고, C++의 operator new()도 대체할 수 있음
    • C가 직접 하게 만드는 점은 좋지만, C와 C++ 중 하나를 선택해야 하는 건 별로였음. C에는 약간의 자동화가 필요하지만, 그게 결국 “클래스가 있는 C” 모드의 C++
      슬픈 건 다른 사람들에게 이 모드를 쓰게 설득할 수 없다는 점임. 그래서 직접 감싸야 하는 날것의 C 인터페이스나, 완전히 이해하려면 은하급 두뇌가 필요한 C++ 인터페이스 중 하나만 남음
      “멤버 추가 → 초기화자 추가 → 종료자 추가 → 훑고 종료자 재검사” 루프나 수명 순서를 머릿속으로 계산하는 데 지쳤던 기억이 있음. C를 한 단어로 떠올리면 “반복작업”임
      C++는 문화가 불필요한 복잡성에 집착하지만 않았다면 훌륭했을 것임. 예전에 “모든 C++ 프로그래머는 마지막 페이지 코드가 C++가 아닌 척하려고 C++ 코드를 산더미처럼 쓴다”는 농담을 했음
    • 이 감정에 완전히 동의해서 Datoviz https://datoviz.org/를 거의 전부 C로 작성했음. C++ 의존성이 있거나 조금 더 복잡한 자료구조가 필요할 때만 C++를 씀
      C의 단순함이 좋음. 객체지향이 없으면 아키텍처 결정은 단순해짐. 구조체에 어떤 데이터를 넣을지, 어떤 함수가 필요한지만 정하면 됨
      가장 불편한 건 수동 메모리 관리지만, 텍스트나 복잡한 자료구조를 다루지 않는 한 그렇게 나쁘지 않음
    • C, Go, Python, Lua를 주로 쓰는 이유는 단순함 때문임. 안타깝게도 주류 언어 대부분은 불필요하게 복잡하다고 봄
      소프트웨어 아키텍처든 프로그래밍 언어 설계든, 복잡하게 만드는 건 쉽지만 단순하게 유지하려면 비전과 절제가 필요함
  • 오래전 C로 프로그래밍을 시작했고, 지금도 몇 달마다 그 뿌리로 돌아가고 싶은 꿈을 꿈. 아주 단순했음. 코드를 쓰면 대략 어떤 명령어로 바뀔지 알 수 있었고, 그대로 나아가면 됐음
    그런데 실제로 C로 프로덕션급 애플리케이션을 작성해 보려 하면, 왜 오래전에 떠났는지 깨닫게 됨. 컴퓨터의 도움 없이 직접 해야 할 일이 너무 많고, 경계 조건과 악의적 사용자 앞에서도 제대로 동작하게 맞춰야 할 것이 너무 많음
    지금 낮은 수준 언어를 고른다면 아마 Ada를 고를 듯함. C와 비슷하지만 여러 면에서 컴파일러가 훨씬 더 많이 도와줌

    • Ada가 처음 발표됐을 때 좋아 보여서 서둘러 읽었지만, 지금까지 접근할 기회는 없었음. 이제 오랜 시간이 지나 Ada가 다시 주목받기 시작하는 건지 궁금함
      당시 가장 좋아하던 언어는 PL/I였고, 주로 CP67/CMS에서 썼음. IBM 360 명령어 집합 위의 가상 머신을 활용한 IBM의 초기 대화형 컴퓨팅 시도였음
      디지털 푸리에 계산, 디지털 필터링, 전력 스펙트럼 추정 등을 설명하는 코드를 썼고, JHU/APL의 해군 관계자에게 보여준 뒤 관련 소프트웨어 입찰에서 단독 공급자가 됐음
      이후 IBM SSP의 루틴 세 개를 호환 대체하는 PL/I 코드를 작성했고, 둘은 O(n^2)에서 O(n log n)으로 바꾸고 하나는 Ford와 Fulkerson 작업을 바탕으로 수치 정확도를 개선했음
      FedEx의 첫 항공편 스케줄링 코드도 작성했는데, 이사회가 스케줄링이 너무 어려울까 걱정했고 지분 투자도 걸려 있었음. 그 코드는 이사회를 만족시켜 자금을 열었고 FedEx를 살렸음
      나중에는 IBM의 AI 소프트웨어 YES/L1의 큰 부분을 구한 코드도 작성했음. PL/I를 정말 좋아했음
      지금은 Microsoft .NET을 씀. Windows에서는 왜 안 쓰겠나 싶음
    • Rust 초보자가 빌림 검사기와 싸우는 비율 중 얼마나 컴파일러가 충분히 정교하지 않아서이고, 얼마나 초보자가 Rust에게 메모리 오류를 컴파일하라고 시도하고 있다는 걸 모르는 탓인지 궁금함
    • “코드를 쓰면 대략 어떤 명령어로 바뀔지 알 수 있었다”는 건 정말 오래전 이야기일 것임. 최적화 컴파일러가 있는 지금은 명령어가 아예 나올지조차 알기 어려움
    • MS-DOS와 Amiga 전성기에는 C 컴파일러가 멍청했고, 어셈블리를 손으로 쓰는 사람이 쉽게 이길 수 있었음
      데모씬과 게임의 C 소스 파일은 인라인 어셈블리가 가득한 glorified 매크로 어셈블러에 가까웠음
    • 그래도 C 컴파일러는 많이 좋아졌고, 새니타이저와 분석기도 많은 실수를 쉽게 잡아낼 수 있음
  • kc3 코드는 이런 모습임. 링크의 예제에서 가져온 코드로, 요청 메서드와 URL에 따라 HTML·Markdown 파일을 찾아 보여주는 라우팅 함수처럼 보임
    https://git.kmx.io/kc3-lang/kc3/_tree/master/httpd/page/app/...

    • 많은 사람이 글을 제대로 읽지 않은 것 같음. 이 글은 기본으로 돌아가거나 C로 회귀하고 복잡성을 버리는 이야기가 아니라, 원래 Lisp에서 개발했던 아이디어를 쓰기 위해 KC3라는 새 프로그래밍 언어를 개발하는 이야기임
    • 그 언어에서 조기 반환이 허용되지 않을 수도 있지만, 허용됐다면 훨씬 읽기 쉬웠을 것임
    • 저자가 Jose Valim에게 깊이 영감을 받고 영향을 받았다고 했으니, 대략적으로 KC3는 C에 대한 Elixir이고 Elixir가 Erlang에 대한 관계와 비슷하다는 뜻인지 궁금함
  • C가 첫 언어였고, 곧 콘솔 앱과 Allegro로 작은 게임을 만들었음. 어떤 면에서는 엄청나게 단순하게 느껴짐
    하지만 돌아가고 싶지는 않음. 빌드 도구와 의존성 관리는 낡은 느낌이고, 어딘가에서 항상 문제가 생김. include와 매크로 시스템은 투박함
    정의되지 않은 동작을 일으키기 쉽고, 다른 컴파일러 버전이나 플래그가 다르게 최적화하면서 나중에야 알아차리기도 함
    Zig가 나에게는 새로운 C임. C 컴파일러를 포함하고, C 헤더를 바로 가져와 래퍼 없이 쓸 수 있음. comptime은 훌륭하고, 빌드 도구·의존성 관리·테스트가 포함되어 있으며 교차 컴파일도 쉬움
    아직 개발 중인 언어를 감수할 수 있다면 꼭 살펴보길 권함. 그 외에는 가비지 컬렉션이 괜찮고 단순한 언어가 필요하면 Go, 성능과 안전성이 정말 필요하면 Rust를 씀

  • 가끔 취미로 C를 씀. 진짜 문제는 지루한 부분이 지나치게 손이 많이 간다는 것임. 예를 들어 귀납적 데이터 타입을 일일이 표현해야 함
    컴파일러 같은 것을 C로 많이 작성한다면 결국 태그드 유니언을 할당하고, 패턴 매칭하고, 반복적으로 다루는 일이 커지는데, 같은 상용구 코드를 계속 쓰는 건 매우 피곤함
    지루함을 줄이려고 생성기를 쓸까 생각했지만 아직 만들지는 않았음. Python, Lua, Scheme 같은 내장 가능한 언어로 먼저 프로토타입을 만들고, 만족하면 C로 구현을 확정하는 방식도 생각해 봄. 그렇지 않으면 구현 부담이 너무 큼
    C로 일회성 프로젝트를 하는 데는 미학적 매력이 있다고 믿어서 어렵다. 컴파일된 크기, 컴파일 속도, 성취감 같은 것들이 있지만 많은 부분은 그냥 지루한 막노동임

    • C를 객체지향처럼 만들수록 잡일이 로그처럼 증가한다는 걸 알게 됨
      단순화하고 스트림 관점으로 생각하면 깔끔하고 괜찮아지기 시작함
  • 프로그래밍 언어를 종교처럼 보는 사람들도 있지만, C가 성공한 이유는 실용적이었기 때문이라고 봄
    안전하지 않고 말도 안 되는 일을 할 수 있는 건 맞지만, 하고 싶은 일을 하는 데 방해하지 않음

    • C가 성공했었다고 생각하지 않음. 지금도 성공 중임. 70년대 언어 중 아직 상위 5개 언어에 드는 게 또 뭐가 있나 싶음
      https://www.tiobe.com/tiobe-index/
    • 마이크로컨트롤러나 임베디드를 하려면, 벤더 지원까지 고려했을 때 C가 여전히 전반적으로 최고의 선택이라고 봄
      Rust와 Ada도 아마 천천히 따라오는 중임
    • 낮은 수준의 Perl 같은 느낌임
    • Unix와 AT&T 독점 덕분이라고 봄
  • 댓글을 읽기 전에 블로그 글을 먼저 읽었고, 다 읽고 나서 “저자가 독자에게 뭘 전달하려는 거지?”라는 의문이 들었음
    판단하기로는, 저자의 프로그램이나 유틸리티가 사람들이 인정한 언어로 작성되지 않았기 때문에 충분히 쓰이지 않았다는 내용에 가까움. 메모리 사용량 문제가 있을 수도 있다고 암시됨
    하지만 재작성 후 배운 점이나 사용자 증가 통계는 없음. 새 기능도 얻지 못했고, 아마 할 수 있었고 시간이 있었기 때문에 연습 삼아 한 작업처럼 보임
    저자가 깨달음을 얻어 이제부터는 C만 쓰겠다는 논지도 제시되지 않음

  • 해피엔딩 없는 너드스나이핑 경고담처럼 읽힘

    • “Ruby on Rails로 돈을 많이 벌고 있었는데, Common Lisp로 옮기고 점점 돈을 덜 벌기 시작했고, C로 옮기면서 너드스나이핑을 당했다”는 흐름처럼 보임
      그래도 더 행복해 보이긴 함. C도 멋지긴 함
    • 모든 프로그래머가 “언어를 직접 써야겠다”는 순간을 겪는 것 같음. 문제의 해결책이 결국 언어 자체로 추상화되는 순간임
  • 앞뒤가 맞지 않음. 킬러 앱이 무엇인지, Common Lisp로 작성된 것이 왜 아무도 실행하지 않는 것과 관련 있는지, 가비지 컬렉터에 어떤 문제가 있었는지, 왜 C만 선택지인지 모르겠음
    원격 코드 실행 취약점들이 정말 VM과 컨테이너 때문이고, 전부 C로 작성돼 있기 때문은 아니라고 확신할 수 있나 싶음. “KC3 코드를 실행해도 보안상 영향은 없다”고 했는데 정말 확실한지도 의문임