글EN中한

마지막 프로그래밍 언어는 인간을 위한 것이 아닐지도 모릅니다

AI가 코드의 주된 작성자가 될 때 프로그래밍 언어가 맞이할 세 가지 미래, 그리고 제가 Haskell에 거는 이유.

읽는 시간21 분
게시일
주제AI 보조 엔지니어링, 프로그래밍 언어, 추상화, 정형 기법, Haskell, 컴파일러, 타입 시스템
요약

프로그래밍 언어는 언제나 그것을 쓰는 사람에 의해 형태가 정해져 왔습니다. 그러니 AI가 코드의 주된 작성자가 된다면, 의도와 기계 사이의 중간 계층도 바뀔 것입니다. 첫 번째 미래는 Vera 같은 명시적인 언어입니다. 모호함을 없애고 기계가 읽을 수 있는 컴파일러 피드백을 주지만, 모델은 여전히 단계별 레시피를 쓰고 있습니다. 두 번째 미래는 제가 hx와 BHC로 걸고 있는 미래로, Haskell 같은 선언적 언어입니다. 모델은 결과가 무엇인지를 기술하고, 타입 검사기는 그것을 증명처럼 검증합니다. 세 번째 미래는 소스 코드를 완전히 건너뜁니다. 모델은 먼저 기존 기계어를 직접 생성하고, 나중에는 인간 독자가 아니라 기계를 위해 설계된 조밀한 실행 형식을 겨냥합니다. 이 셋은 경쟁자가 아니라 하나의 시간선 위에 놓인 지점들입니다. 소스 코드는 사라지지 않고 전문화될 것입니다. 어셈블리가 인프라를 유지하는 사람들의 영역이 된 것처럼 말입니다.

오늘 아침 저는 책상 앞에 서서 물을 탄 인스턴트커피를 마시며, 매일 아침 긴급한 이메일과 알림을 정리한 뒤에 늘 하는 일을 하고 있었습니다. HackerNews를 30분 동안 읽는 것입니다. 시간을 정해 두고 하는 의식이고, 거르는 일은 없습니다. 저는 매일 사무실에 나가는데, 그 책상 앞이든 집 책상 앞이든 아침은 똑같습니다. 커피, 자세, 첫 페이지.

HackerNews는 지금도 베이 에어리어, 기술, 과학, 그리고 밤사이 떠오른 지적으로 자극적인 생각이 무엇이든 그 맥박을 짚는 가장 좋은 방법 가운데 하나입니다. 엄선된 뉴스레터도 몇 개 구독하지만, 높은 신호와 낮은 신호를 가려낼 줄만 알면 HN이 그 내용 대부분을 어차피 다룬다는 것을 몇 해에 걸쳐 알게 되었습니다. 매일 다른 링크 하나씩을 두고 글을 쓸 수도 있을 것입니다. 대부분의 아침에는 참습니다. 오늘 아침은 참지 않았습니다.

링크 하나가 눈에 들어왔습니다. Vera라는 새 프로그래밍 언어로, “인간이 아니라 기계가 쓰도록 설계된” 언어라고 합니다. 정적 타입이고, 순수 함수형이며, WebAssembly로 컴파일되고, 계약 검증에 Microsoft의 Z3 솔버를 씁니다. 페럿 마스코트도 있습니다. 저는 기술 프로젝트의 동물 마스코트를 좋아합니다. Rust의 게 Ferris, Go의 고퍼, Zentinel의 수호견 시사(Shisa)가 그렇습니다. 페럿은 좋은 선택입니다.

하지만 제가 스크롤을 멈춘 이유는 마스코트가 아니었습니다. 제가 지난 12월에 도달한 것과 같은 결론에 다른 누군가도 도달했기 때문입니다. 기존의 프로그래밍 언어는 이 새로운 기술의 능력에 맞춰져 있지 않으며, 무언가가 바뀌어야 한다는 결론입니다.

크리스마스의 깨달음

저는 Anthropic이 Claude Code를 처음 내놓은 2025년 5월부터 유료 구독자였습니다. 초기의 사용량 제한과 모델 품질은 아쉬움을 남겼지만, CLI 중심이라는 방향은 곧바로 납득이 갔습니다. 2025년 12월에는 Max 구독으로 올렸고, 휴가 중이었으며, Anthropic은 그달 일일 한도를 넉넉하게 풀어 두었습니다. 저는 몇 해에 걸쳐 쌓아 둔 아이디어를 하나도 남김없이 태워 버리고 있었습니다. 좋은 것도 있었고, 형편없는 것도 있었습니다. 그래도 모두 마침내 예전에는 불가능했던 방식으로 시험해 볼 수 있었습니다. 속도를 따라오는 모델과 페어 프로그래밍을 할 수 있었기 때문입니다. 그 변화에 대해서는 How I Work These Days에 더 자세히 썼는데, 짧게 말하면 2025년 말은 저에게 야망과 실행 사이의 관계가 근본적으로 바뀐 시기였습니다. 둑이 무너졌습니다. 몇 해 동안 공책 속에 머물던 아이디어들이 며칠 만에 실제 소프트웨어가 되기 시작했습니다.

컴파일러 설계를 두고 Claude Code와 깊은 대화를 나누던 그런 늦은 밤의 작업 중 하나에서, 한 가지 생각이 또렷해졌습니다. 저는 hx를 만들면서 AI가 사람들이 Haskell을 쓰는 방식을 어떻게 바꿀지 생각하고 있었는데, 그 질문이 Haskell보다 크다는 것을 깨달았습니다. 에이전트의 도움을 받는 소프트웨어 개발은 출력 언어 자체, 즉 정보가 어떻게 처리되어야 하는지를 표현하는 데 우리가 쓰는 중간 계층이 근본적으로 바뀌는 지점에 다가가고 있습니다.

이것은 추상적인 관찰이 아닙니다. 소프트웨어의 역사는 추상화 계층이 쌓여 온 역사이고, 각 계층은 다음 세대의 프로그래머가 이전 세대가 숙달해야 했던 것을 무시할 수 있게 해 주었습니다.

시작은 기계어였습니다. CPU 아키텍처마다 다른 날것의 옵코드입니다. 소프트웨어를 쓰고 싶으면 명령어 집합을 외워서 손으로 썼습니다. 사람들은 두꺼운 참조 매뉴얼을 펴냈고, 명령어 집합 아키텍처 전체를 머릿속에 담고 있는 엔지니어들도 있었습니다. 그중 몇몇은 아직 활동하고 있고, 몇몇은 여전히 그 방법론을 고집합니다.

그다음 어셈블러가 그 옵코드에 사람이 읽을 수 있는 이름을 붙였습니다. 89 D8 대신 MOV AX, BX라고 쓰는 식입니다. 여전히 특정 아키텍처를 위해 썼고 여전히 레지스터 단위로 생각했지만, 이제 자신이 쓴 것을 읽을 수 있었습니다. 첫 번째 추상화는 새로운 능력이 아니었습니다. 그것은 가독성이었습니다.

그다음 C가 등장해 하드웨어 위에 이식 가능한 추상화를 주었습니다. 레지스터 단위로 생각하기를 그만두고 함수와 포인터 단위로 생각하기 시작했습니다. C는 아키텍처별 어셈블리로 컴파일되었지만, 어떤 아키텍처인지 신경 쓸 필요는 없었습니다. 하나의 언어, 여러 타깃입니다. 참조 매뉴얼은 명령어 집합에서 언어 명세로 옮겨 갔습니다.

그다음 인터프리터와 가상 머신이 또 하나의 계층을 더했습니다. JVM, Python, Perl입니다. 메모리 배치를 생각하기를 그만두고 객체, 이터레이터, 가비지 컬렉션을 생각하기 시작했습니다. 추상화는 더 두꺼워졌고, 피드백 루프는 더 빨라졌으며, 사용자층은 하드웨어 엔지니어에서 스크립트를 쓸 줄 아는 누구에게나로 넓어졌습니다.

그다음 IDE가 언어 자체와 상호작용하는 방식을 바꾸었습니다. 문법 강조, 자동 완성, 통합 디버거, 리팩터링 도구가 생겼습니다. 편집기가 대신 들고 있어 주었기 때문에 API 전체를 머릿속에 담고 있을 필요가 없어졌습니다. 언어는 바뀌지 않았지만, 그것을 쓰는 인지적 비용은 낮아졌습니다. IntelliSense는 언어 기능이 아니었습니다. 그것은 프로그래머의 기억 위에 놓인 추상화였습니다.

그다음 인터넷이 코드가 움직이는 방식을 바꾸었습니다. 오픈 소스 저장소, 패키지 관리자, 공유 라이브러리가 생겼습니다. 모든 것을 처음부터 쓰기를 그만두고 다른 사람들이 만든 부품을 조합하기 시작했습니다. 참고 자료는 인쇄된 책에서 웹 문서, 위키, 튜토리얼로 옮겨 갔습니다.

그다음 StackOverflow가 사람들이 배우는 방식을 바꾸었습니다. 매뉴얼을 처음부터 끝까지 읽는 대신, 지금 겪는 바로 그 문제를 검색해서 이미 그것을 해결한 사람을 찾았습니다. 지식 계층 자체가 하나의 추상화가 되었습니다. 시스템 전체를 이해할 필요는 없었습니다. 맞는 답을 찾아 자신의 맥락에 맞게 고치기만 하면 되었습니다. StackOverflow는 코드 한 줄도 컴파일한 적이 없지만, 인간의 혼란과 작동하는 소프트웨어 사이에 놓인 중간 계층이었고, 같은 10년 동안 나온 어떤 언어 기능보다도 평균적인 개발자의 생산성에 더 중요했다고 말할 수도 있습니다.

그리고 이제 StackOverflow는 물러나고 있습니다. 답이 나빠져서가 아닙니다. 다음 추상화 계층이 도착했기 때문입니다. AI 코딩 에이전트는 StackOverflow가 하던 일, 즉 알려진 문제에 대해 알려진 해법을 찾는 일을 합니다. 그런데 StackOverflow가 결코 할 수 없던 일도 합니다. 새로운 해법을 종합하고, 여러 파일에 걸친 프로젝트 맥락을 유지하며, 의도를 설명한 문장에서 작동하는 코드를 생성합니다. 패턴은 이전의 모든 전환과 같습니다. 각 계층은 이전 계층을 대체해서가 아니라 더 높은 수준의 추상화 안으로 흡수함으로써 그것을 덜 필수적으로 만듭니다.

프로그래밍 언어는 언제나 그것을 쓰는 사람에 의해 형태가 정해져 왔습니다. 어셈블리는 레지스터와 옵코드로 생각하던 하드웨어 엔지니어들이 빚었습니다. C는 메모리 위에 이식 가능한 추상화가 필요했던 시스템 프로그래머들이 빚었습니다. Python은 문법과 씨름하지 않고 일을 해내고 싶었던 사람들이 빚었습니다. COBOL은 영어처럼 읽히는 코드를 원했던 비즈니스 분석가들이 빚었습니다. 모든 언어에는 의도된 작성자의 지문이 남아 있습니다. AI가 코드의 주된 작성자가 된다면, 언어가 그 새 작성자의 강점과 약점에 맞춰 적응해야 한다는 결론이 따라 나옵니다. 이것은 패턴에서 벗어나는 일이 아닙니다. 패턴이 늘 해 오던 일을 하는 것입니다.

저는 계속 세 가지 구체적인 가능성으로 되돌아왔습니다. 중간 계층이 진화할 수 있는 세 가지 방식입니다. 정확히 말하면 서로 경쟁하는 비전은 아닙니다. 오히려 하나의 시간선 위에 놓인 세 지점에 가깝습니다.

기계를 위해 언어를 충분히 명시적으로 만들기

코딩 에이전트와 실제로 시간을 보내 본 사람이라면 누구나 그 실패 양상을 보았을 것입니다. LLM에게 Python 함수를 써 달라고 합니다. 출력은 그럴듯해 보입니다. 린팅을 통과합니다. 변수 이름도 합리적입니다. 구조도 흔한 패턴을 따릅니다. 그런데 모델이 추적하지 못한 미묘한 암묵적 동작 때문에 런타임에 실패합니다. 호출 사이에 공유되는 가변 기본 인자. 두 번째 순회에서 조용히 소진되는 제너레이터. 어떤 라이브러리 작성자가 그쪽이 더 “Pythonic”하다고 판단했다는 이유로 예외를 던지는 대신 None을 반환하는 메서드. 모델은 알고리즘에 대해서는 틀리지 않았습니다. 언어의 숨은 동작에 대해 틀렸습니다.

이것이 Vera가 풀려는 문제이고, 제가 고민하던 첫 번째 접근법이기도 합니다. Go의 단순함과 명시성을 가져와 더 밀어붙이고, 모든 명령어, 모든 메서드, 모든 디자인 패턴이 최대한 모호하지 않은 언어를 설계하는 것입니다. 암묵적 동작도 없고, 이름의 모호함도 없고, 스타일의 선택지도 없습니다. 모든 것을 쓰는 표준적인 방법이 하나뿐이어서, LLM이 열일곱 가지 유효한 접근법 중 무엇을 택할지 따지느라 추론 토큰을 낭비할 필요가 없습니다.

그 언어에는 뛰어난 컴파일러 진단이 필요할 것입니다. 단순히 “47번째 줄에서 타입 불일치”가 아니라, 모델이 파싱하고 이해하고 곧바로 행동에 옮길 수 있는 구조화된 피드백입니다. Rust와 Elixir는 이미 인간을 위해 이것을 잘 해냅니다. 그것을 한층 더 잘, 그리고 기계를 위해 해내는 것입니다.

파이프라인은 다음과 같습니다.

+-------+     prompt      +-------+    explicit     +-----------+
| Human | ------------->  |  LLM  | ------------->  | Verifying |
+-------+                 +-------+    source       | Compiler  |
                             ^                      +-----------+
                             |                           |
                             |   structured error        |
                             |   + suggested fix         |
                             +---------------------------+
                                                         |
                                                    verified
                                                         |
                                                         v
                                                  [correct program]

핵심은 피드백 루프입니다. 컴파일러는 나쁜 코드를 거부하는 데서 그치지 않습니다. 무엇이 잘못되었는지를 모델이 행동으로 옮길 수 있는 말로, 구체적인 수정 제안과 함께 설명합니다. 모델은 다시 생성합니다. 컴파일러는 다시 검사합니다. 반복을 통해 올바른 코드로 수렴하며, 그 루프가 얼마나 촘촘한지는 언어가 얼마나 모호하지 않은지, 오류가 얼마나 행동으로 옮기기 쉬운지에 달려 있습니다.

Vera는 바로 이 아이디어를 확신을 가지고 실행한 것입니다. 함수는 이렇게 생겼습니다.

public fn safe_divide(@Int, @Int -> @Int)
  requires(@Int.1 != 0)
  ensures(@Int.result == @Int.0 / @Int.1)
  effects(pure)
{
  @Int.0 / @Int.1
}

변수 이름이 아예 없습니다. 매개변수는 De Bruijn 슬롯 표기법을 써서 타입과 위치 인덱스로 참조합니다. @Int.0은 가장 최근에 바인딩된 정수이고, @Int.1은 그 앞의 것입니다. 모든 함수는 사전 조건(requires), 사후 조건(ensures), 부수 효과(effects)를 선언해야 합니다. 컴파일러는 가능한 곳에서는 Z3로 계약을 정적으로 검증하고, 컴파일 시점에 판정할 수 없는 것은 런타임 검사로 넘깁니다.

설계 원칙은 날카롭습니다. 모델이 옳을 필요는 없고, 검사 가능하기만 하면 됩니다. 언어가 유효한 프로그램의 공간을 아주 좁게 제약하기 때문에, 컴파일러는 실행 전에 실수를 잡아내고 그것을 자연어로 설명합니다. 0으로 나누기는 런타임 예외가 아닙니다. 컴파일 중에 잡히는 계약 위반입니다.

이렇게 생각해 보십시오. 전통적인 언어는 열린 들판입니다. 어느 방향으로든 걸을 수 있고, 쓸모 있는 곳에 닿을 수도 있지만 절벽에서 떨어질 수도 있습니다. Vera는 난간이 있는 안내된 길입니다. 정해진 방향으로만 갈 수 있고, 길에서 벗어나려 할 때마다 표지판이 대신 정확히 어디로 디뎌야 하는지 알려 줍니다. 열린 들판 위의 LLM은 헤맵니다. 안내된 길 위의 LLM은 수렴합니다.

초기 벤치마크 결과는 흥미롭지만 엇갈립니다. Kimi K2.5는 Vera 코드를 완벽하게 쓰는 것으로 보입니다. VeraBench에서 100%를 기록해 자신의 Python과 TypeScript 점수를 넘어섰습니다. 다른 모델들은 그만큼 잘하지 못합니다. Claude Opus 4는 Python에서 96%를 기록한 반면 Vera에서는 88%를 기록했습니다. Vera 팀은 이 편차에 대해 솔직하고, 저는 그 점을 높이 삽니다. HackerNews 토론은 빈약해서, 12점에 회의적인 댓글 세 개뿐이었습니다. 이 대화가 아직 얼마나 초기인지 알 수 있습니다. 더 넓은 개발자 커뮤니티는 아직 이 아이디어를 진지하게 다루지 않았습니다. 그것은 바뀔 것입니다.

하지만 기계가 해법을 반복해 가는 방식에 맞춰 언어를 최적화하는 데에는 마음에 걸리는 점이 있습니다. 파이프라인 다이어그램을 다시 보십시오. LLM은 여전히 단계별 명령을 생성하고 있습니다. 모호함을 걷어내고 계약을 명시했을 뿐, 여전히 일을 어떻게(HOW) 하는지를 기술하고 있습니다. 기계는 여전히 과정을 순차적으로 따라가며 추론합니다. 피드백 루프의 잡음은 줄였지만, 신호의 성격은 바꾸지 않았습니다. 모델은 여전히 레시피를 쓰고 있습니다. 더 정밀한 레시피일 뿐입니다.

레시피 쓰기를 아예 그만둔다면 어떨까요?

어떻게가 아니라 무엇을 기술하기

두 번째 아이디어는 다른 전제에서 출발합니다. 절차적 코드를 AI가 올바르게 쓰기 쉽게 만드는 대신, AI에게 표현하라고 요구하는 것 자체를 바꾸는 것입니다.

구체적으로 말해 보겠습니다. 로그에서 가장 최근의 서버 오류 열 개를 찾아야 한다고 합시다. 절차적 언어로 그 과정을 기술하면 이렇게 됩니다.

errors = []
for entry in log_entries:
    if entry.status >= 500:
        errors.append({
            "time": entry.timestamp,
            "path": entry.path,
            "code": entry.status
        })
errors.sort(key=lambda e: e["time"], reverse=True)
return errors[:10]

기계에게 이렇게 말하고 있는 것입니다. 빈 리스트를 만들어라. 각 항목을 하나씩 훑어라. 조건을 검사하라. 맞으면 딕셔너리를 만들어 덧붙여라. 그다음 쌓인 리스트를 키로 정렬하라. 그다음 처음 열 개 요소를 가져가라. 모든 단계가 명령입니다. 기계는 가변 리스트, 순회 상태, 정렬, 슬라이스를 추적해야 합니다. 그리고 어느 한 단계를 틀려도 나머지는 성공할 수 있어서, 올바라 보이지만 미묘하게 망가진 출력이 나옵니다. reverse=True 하나가 빠지면 가장 최근 오류 대신 가장 오래된 오류를 조용히 받게 됩니다.

같은 일을 Haskell로 쓰면 이렇습니다.

recentErrors :: [LogEntry] -> [ErrorSummary]
recentErrors =
    take 10
  . sortBy (flip compare `on` time)
  . map toSummary
  . filter isServerError

아래에서 위로 읽으십시오. 서버 오류를 걸러 내고, 각각을 요약으로 변환하고, 시간 역순으로 정렬하고, 열 개를 가져갑니다. 가변 누산기가 없습니다. 루프 변수도 없습니다. 중간 상태도 없습니다. 과정을 기술하는 것이 아닙니다. 입력과 출력 사이의 관계를 기술하는 것입니다. 이 함수는 결과를 단계별로 어떻게 계산하는지가 아니라, 결과가 무엇인지(IS)를 말합니다.

맨 위의 타입 시그니처 [LogEntry] -> [ErrorSummary]는 컴파일러가 강제하는 계약입니다. toSummary가 잘못된 타입을 반환하거나, isServerError가 LogEntry를 받지 않거나, 실수로 타입 검사를 통과하지 못하는 순서로 함수를 합성하면, 컴파일러는 프로그램이 실행되기 전에 그것을 거부합니다. 런타임의 모호한 “object has no attribute”가 아닙니다. 어느 조각이 맞지 않는지를 정확히 알려 주는, 컴파일 시점의 정밀한 타입 오류입니다.

이 차이는 AI에게 엄청나게 중요합니다. 각 경우에 LLM이 실제로 무엇을 추적해야 하는지 생각해 보십시오.

Procedural (Vera, Python, Go)          Declarative (Haskell)
================================       ================================
- mutable variables and their          - input type
  current state at each step           - output type
- loop iteration progress              - which transformations to compose
- conditional branching outcomes       - whether types align
- order of side effects
- implicit language behaviors
- names and what they refer to

절차적 모델은 AI에게 머릿속으로 실행을 시뮬레이션하라고 요구합니다. 선언적 모델은 AI에게 변환을 기술하고 그 검증은 컴파일러에게 맡기라고 요구합니다. 하나는 LLM의 약점(여러 단계에 걸친 상태 추적)을 시험하고, 다른 하나는 강점(형식적 제약을 만족하는 패턴을 알아보고 생성하는 일)을 살립니다.

파이프라인은 근본적으로 바뀝니다.

+-------+     prompt      +-------+   type sigs      +-----------+
| Human | ------------->  |  LLM  | ------------->    | Compiler  |
+-------+                 +-------+   pure exprs      +-----------+
                                                           |
                                                      types align?
                                                        /      \
                                                      yes       no
                                                      |          |
                                                      v          v
                                              [proven correct]  [precise type error:
                                                                 "Expected LogEntry,
                                                                  got String at ...]

순조로운 경로에서는 피드백 루프가 필요 없습니다. 타입이 맞으면, 타입 시스템이 추적하는 속성에 관한 한 그 프로그램은 구성상 올바릅니다(correct by construction). 컴파일러는 모델과 함께 반복하고 있는 것이 아닙니다. 증명을 검사하고 있는 것입니다.

이것이 제가 거는 접근법입니다. 이 블로그가 존재하는 이유이기도 합니다.

이 사이트 뒤에 있는 이름 Raskell은 Rascal(라쿤라고 할 때의 그 Rascal로, 라쿤는 제 마스코트입니다)과 Haskell을 합친 말입니다. Haskell은 수학적 증명에 가까운 것들을 얼마나 우아하게 기술하는지 때문에 제가 몇 해 동안 사랑해 온 언어입니다. TODO가 아니라 QED입니다. 타입이 잘 맞는 Haskell 함수를 쓸 때, 여러분은 단지 코드를 쓰는 것이 아닙니다. 어떤 변환이 유효하다는 증명을 쓰는 것이고, 컴파일러는 그 증명 검사기입니다.

하지만 Haskell을 사랑하는 것과 Haskell을 프로덕션에 내보내는 것은 다른 경험이며, 그 사이의 간극은 대부분 도구에 있습니다. 생태계는 10년 넘게 사람들을 좌절시켜 온 방식으로 파편화되어 있습니다. cabal, stack, ghcup. 의존성이 어떻게 동작해야 하는지에 대해 서로 다른 의견을 지닌 채 겹치는 일을 하는 세 도구입니다. Python에서 오셨다면, pip, poetry, pyenv가 모두 따로 개발되어 락파일 형식도 다르고 리졸버 알고리즘도 다르며 가끔 서로 호환되지 않는다고 상상해 보십시오. 그것이 Haskell이었습니다. 빌드는 느렸습니다. 오류 메시지는 도움이 되는 것부터 암호 같은 것까지 다양했습니다. 런타임은 하나의 성능 프로파일이 모든 용도에 맞는다고 가정했습니다. 엣지 함수나 임베디드 시스템, GPU 가속 수치 계산에 Haskell을 쓰고 싶다면, 실제 코드를 쓰는 만큼의 시간을 툴체인과 씨름하는 데 썼습니다.

언어는 옳았습니다. 그것을 둘러싼 인프라가 그렇지 못했습니다.

그래서 저는 astral.sh가 Python 도구에 가져온 것과 같은 접근으로 arcanist.sh를 만들기 시작했습니다. astral.sh가 uv와 ruff를 내놓았을 때, 굳어진 도구를 지닌 성숙한 생태계라도 개발자 경험을 Rust로 처음부터 다시 만들어 모든 것을 극적으로 더 빠르고 일관되게 만들 수 있다는 것을 보여 주었습니다. 저는 Haskell에 같은 일을 하고 싶었습니다. arcanist.sh에는 두 프로젝트가 있습니다.

hx는 Rust로 만든, 빠르고 분명한 주관을 지닌 Haskell용 차세대 툴체인입니다. 파편화된 스택을 대체하는 하나의 도구입니다. 프로젝트별로 고정되는 관리형 컴파일러 버전. 지문 검증을 갖춘 결정론적 TOML 락파일. cabal보다 5.6배 빠른 콜드 빌드. 7.8배 빠른 증분 재빌드.

curl -fsSL https://arcanist.sh/install.sh | sh
hx new my-app && cd my-app
hx run

ghcup도, stack도, cabal-install도 없습니다. 그냥 hx입니다.

BHC, 즉 Basel Haskell Compiler는 한 걸음 더 나아갑니다. GHC의 포크가 아니라 Rust로 백지에서 새로 쓴 Haskell 컴파일러이며, Haskell 2026 Platform 명세를 겨냥합니다. 네이티브 코드 생성에 LLVM을 쓰고, 컴파일 시점에 고르는 여섯 가지 런타임 프로파일을 제공합니다.

프로파일용도핵심 특성
default일반 애플리케이션지연 평가, GC 관리, GHC 호환 의미론
server백엔드 서비스구조적 동시성, 자동 취소, 관측 가능성 훅
numericML과 과학 계산엄격한 수치 연산, 텐서 로어링(lowering), SIMD, GPU 백엔드(CUDA/ROCm)
edgeWASM과 CDN 워커최소한의 풋프린트, LLVM 없이 WASM 직접 생성
realtime게임, 오디오, 로보틱스1ms 미만으로 제한된 GC 일시 정지, 아레나 할당
embedded베어 메탈, 마이크로컨트롤러GC 전혀 없음, 정적 할당, ARM Cortex-M 같은 타깃

같은 Haskell 소스라도 무엇을 만드느냐에 따라 런타임 계약이 달라집니다. 보안 정책 엔진은 server 프로파일로 컴파일되어 트레이싱을 갖춘 구조적 동시성을 얻습니다. 텐서 파이프라인은 numeric 프로파일로 컴파일되어 GPU 가속을 얻습니다. 엣지 함수는 WASM으로 컴파일되어 Cloudflare Workers에서 실행됩니다. 같은 언어. 같은 타입 안전성. 다른 성능 범위.

이 작업 뒤에 있는 확신은 구체적입니다. AI가 코드를 쓸 때 살아남는 언어는 절차적 명시성에 최적화된 언어가 아닙니다. 무언가를 어떻게 계산하는지가 아니라 그것이 무엇인지를 기술함으로써 일관성과 순수성을 가져오는 언어입니다. AI는 형식적 제약을 만족하는 표현식을 생성하는 데 대단히 뛰어납니다. 그리고 증명처럼 읽히는 소스 코드는 시간을 견딜 수 있습니다. 메인테이너의 번아웃을 견딜 수 있습니다. 3년 뒤에는 어떤 함수가 왜 그렇게 쓰였는지 아무도 기억하지 못한다는 사실도 견딜 수 있습니다. 타입은 기억합니다. 타입이 의도를 담고 있기 때문에, 증명은 절차적 코드가 결코 그럴 수 없는 방식으로 스스로를 문서화합니다.

Vera와 arcanist.sh는 같은 전제를 받아들입니다. 중간 계층이 바뀌고 있다는 것입니다. 둘은 그것이 어느 방향으로 바뀌어야 하는지에 대해 의견이 다릅니다. Vera는 생성 루프의 오류를 줄이는 데 최적화합니다. 가치 있는 일입니다. 하지만 hx와 BHC는 생성된 코드를 구성상 올바르게 만드는 데 최적화합니다. 언어 자체가 구조적인 수준에서 유효한 프로그램의 모습을 제약하기 때문입니다.

언어를 완전히 건너뛰기

세 번째 가능성은 제가 늦은 밤에 생각하는 것이고, 구체적인 프로젝트는 아직 없습니다. 아직은요. 제가 가장 매혹적으로 느끼는 동시에 가장 불안하게 느끼는 가능성이기도 합니다.

AI가 소스 코드를 아예 쓰지 않게 된다면 어떨까요?

이것이 왜 그럴듯한지 이해하려면 소스 코드가 실제로 무엇인지 살펴보는 것이 도움이 됩니다. 소스 코드는 최종 제품이 아닙니다. 한 번도 그런 적이 없습니다. 소스 코드는 컴파일러가 기계어로 변환하는 명령의 집합입니다. 하드웨어가 실행하는 것은 기계어입니다. 소스 코드가 존재하는 이유는 인간에게 자신의 의도와 실리콘 사이의 추상화 계층이 필요했기 때문입니다. 우리는 “이 리스트를 정렬하라”나 “권한 없는 요청을 거부하라” 같은 개념으로 생각합니다. CPU는 레지스터 이동, 메모리 로드, 조건부 점프로 생각합니다. 소스 코드는 그 간극을 잇습니다.

하지만 그 다리는 인간 작성자를 위해 지어졌습니다. 작성자가 더 이상 인간이 아니라면, 그 다리는 다른 목적을 갖게 됩니다. 감사 추적이 됩니다. 인간이 AI가 만든 것을 읽고 검증하는 수단입니다. 저작의 매체가 아니라 투명성의 계층입니다.

저는 이것이 두 개의 뚜렷한 단계로 펼쳐질 것이라고 봅니다.

1단계: 기존 기계어를 겨냥하는 AI

첫 번째 단계는 대부분의 사람들이 생각하는 것보다 가까이 있고, 개념적으로는 단순합니다. 우리는 이미 소스 코드로 모델을 훈련합니다. 컴파일된 산출물로도 대규모로 훈련하면 어떻게 될까요? 바이너리, 오브젝트 파일, 중간 표현, 즉 컴파일러의 실제 출력으로 말입니다.

컴파일러가 하는 일을 생각해 보십시오. 컴파일러는 소스 코드를 받아 잘 정의된 결정론적 규칙에 따라 기계 명령으로 변환합니다. 소스 패턴과 출력 패턴 사이에는 대응 관계가 있습니다. C의 for 루프는 x86에서 비교, 분기, 증가 명령의 특정한 순서가 됩니다. 함수 호출은 특정한 호출 규약을 따릅니다. 메모리 할당은 특정한 시스템 콜 패턴을 따릅니다. 이런 대응 관계는 학습할 수 있습니다. 그것들은 패턴이고, 패턴 인식이야말로 LLM이 뛰어난 분야입니다.

Today:
human intent → prompt → LLM → source code → compiler → x86/ARM → CPU

Phase one:
human intent → prompt → LLM → x86/ARM directly → CPU
                                (trained on source +
                                 compiled artifact pairs)

이 단계에서 모델은 소스 코드 계층을 건너뛰고 기계어를 직접 생성합니다. 전통적인 의미의 “컴파일”을 통해서가 아닙니다. 의도의 설명에서 유효한 실행 파일을 만들어 낼 만큼 패턴을 충분히 잘 학습했기 때문입니다. 유창한 번역가가 문법 규칙을 의식적으로 분석하지 않고 의미에서 곧바로 올바른 문장을 만들어 내는 것과 같은 방식입니다.

이것은 급진적으로 들리지만, 우리가 이미 출력을 읽지 않는 컴파일러를 신뢰하고 있다는 것을 떠올리면 달라집니다. gcc -O3의 어셈블리 출력을 들여다보며 C 프로그램이 올바르게 컴파일되었는지 확인한 것이 언제였습니까? 여러분은 컴파일러를 신뢰합니다. 결과 바이너리의 동작을 테스트합니다. 중간 표현을 감사하지는 않습니다. AI가 같은 동작 테스트를 통과하는 바이너리를 만들어 낼 수 있다면, “AI가 생성한 기계어”와 “컴파일러가 생성한 기계어” 사이의 실질적인 차이는 근본적인 가능성의 문제가 아니라 신뢰를 어떻게 보정하느냐의 문제가 됩니다.

제가 계속 되돌아가는 비유는 항공입니다. 초기 조종사들은 손으로 비행했고 항공기의 모든 기계 장치를 이해했습니다. 플라이 바이 와이어가 그것을 바꾸었습니다. 조종사는 의도(상승, 선회, 고도 유지)를 전달합니다. 컴퓨터는 그것을 조종면의 움직임으로 옮깁니다. 조종사는 돌풍이 불 때마다 에일러론과 엘리베이터를 손으로 조정하지 않습니다. 시스템을 신뢰합니다. 중간 단계가 아니라 결과(고도, 방위, 대기 속도)를 확인합니다. 언어 이후 프로그래밍의 1단계는 소프트웨어를 위한 플라이 바이 와이어입니다.

이것이 추측처럼 들린다면, 이번 주에 일어난 일을 생각해 보십시오. Anthropic은 AWS, Apple, Google, Microsoft, NVIDIA, CrowdStrike, Palo Alto Networks, Linux Foundation 등이 참여하는 연합인 Project Glasswing을 발표했습니다. AI를 활용해 세계에서 가장 중요한 소프트웨어를 지키기 위해 결성된 연합입니다. Anthropic의 CEO Dario Amodei는 이렇게 분명히 말했습니다.

“AI 모델은 소프트웨어 취약점을 찾아내고 악용하는 데 있어 가장 숙련된 인간을 제외한 모두를 능가할 수 있는 수준의 코딩 능력에 도달했습니다.”

그가 제시한 증거는 이것이었습니다. “OpenBSD에서 27년 동안 존재해 온 버그를 찾았습니다.”

그것이 무엇을 뜻하는지 생각해 보십시오. OpenBSD는 세계에서 가장 꼼꼼하게 감사되는 코드베이스 가운데 하나입니다. 살아 있는 가장 세심한 시스템 프로그래머들 일부가 수십 년 동안 보안에 초점을 맞춰 검토해 왔습니다. 그런데 AI 모델이 27년 동안 모든 인간 검토자가 놓친 것을 찾아냈습니다. AI가 인간이 찾지 못하는 취약점을 찾아낼 만큼 기존 코드를 깊이 이해할 수 있다면, 사람이 읽을 수 있는 소스를 중간 단계로 거치지 않고 코드를 생성할 만큼도 깊이 이해할 수 있습니다. 질문은 더 이상 AI가 구조적인 수준에서 코드를 이해하느냐가 아닙니다. 그 질문에는 이번 주에 답이 나왔습니다. 질문은 그 이해로 다음에 무엇을 하느냐입니다.

2단계: 새로운 종류의 기계

두 번째 단계는 더 멀리 있고 더 추측에 가깝습니다. 하지만 저는 결국 그쪽으로 가게 될 것이라고 생각합니다.

AI가 기계가 실행할 코드를 생성하고 인간은 아무도 그것을 읽을 필요가 없다면, 그 코드가 인간의 이해를 위해 설계된 명령어 집합을 겨냥할 이유가 없습니다. x86과 ARM은 누군가가 적어도 가끔은 명령어를 들여다볼 것이라는 가정 아래 설계되었습니다. 니모닉이 있습니다. 디스어셈블을 가능하게 하는 규약을 따릅니다. 인간이 이해하는 연산에 대략 대응하는 명령어들로 구성되어 있습니다.

그런데 실행 타깃이 AI가 생성하는 코드를 위해 처음부터 설계된다면 어떨까요? 새로운 종류의 바이트코드를 소비하는 가상 머신이나 런타임입니다. 사람이 읽기 좋도록 최적화되지도 않았고, 손으로 쓰는 어셈블리에 맞춰 최적화되지도 않았습니다. 오로지 실행 밀도와 기계 생성에만 최적화되어 있습니다.

Phase two:
human intent → prompt → AI → dense symbolic bytecode → AI-native VM
                               (opaque to humans,         |
                                optimized for machine     v
                                generation + execution)  [result]

저는 정보 밀도에 대해 계속 생각합니다. 한자는 라틴 알파벳 단어보다 훨씬 큰 의미의 무게를 지닌 개별 기호에 의미를 담습니다. 글자 하나가 영어로는 한 구절 전체가 필요한 개념을 나타낼 수 있습니다. 조밀한 기호를 자연스럽게 처리할 수 있는 독자를 위해 시스템이 설계되면, 표현은 압축됩니다. 설계 대상에 속하지 않았던 독자에게는 덜 접근하기 쉬워지는 대가로 더 효율적이 됩니다.

AI 네이티브 바이트코드도 같은 원리를 따를 수 있습니다. 각 명령어가 기존 명령어 수십 개로 표현해야 할 복잡한 복합 연산을 담을 수 있습니다. 그 바이트코드는 지금의 기계어가 장황해 보일 만큼 조밀할 것입니다. 디컴파일하거나 분석하면 완전히 불투명할 것입니다. 일부러 난독화한 것이 아닙니다. 훈련된 신경망의 가중치가 실제로 기능하는 지식을 담고 있으면서도 이해할 수 없는 것과 마찬가지로, 인간의 인지로는 본래 이해할 수 없을 뿐입니다.

이 바이트코드를 실행하는 가상 머신은 그 자체로 다른 종류의 시스템일 것입니다. 전통적인 의미의 스택 머신이나 레지스터 머신이 아닙니다. 어쩌면 바이트코드가 순차적인 명령이 아니라 변환 그래프를 기술하는 데이터플로 엔진에 더 가까운 무언가일 것입니다. 누군가에게 경로를 하나하나 알려 주는 운전 안내(북쪽으로 가서, 왼쪽으로 돌고, 2마일 계속 가라)를 주는 것과, 목적지가 표시된 지도를 건네는 것의 차이로 생각해 보십시오. 바이트코드는 지도입니다. 경로는 VM이 찾아냅니다.

우리가 지금 어디에 있는지는 솔직하게 말하고 싶습니다. 우리는 1단계에 와 있지 않습니다. 2026년 4월에는 아닙니다. 지금의 모델은 여전히 중간 계층이 필요합니다. 단계별로 추론해 나갈 수 있을 때 더 나은 코드를 만듭니다. 타입 시스템과 계약과 명시적인 오류 메시지에서 도움을 받습니다. 첫 번째와 두 번째 접근법은 단지 실현 가능한 정도가 아니라, 지금 당장 필요합니다.

하지만 궤적은 보입니다. 모델은 세대마다 올바른 프로그램을 생성하는 데 더 나아지고 있습니다. 형식 검증은 더 실용적이 되고 있습니다. 하드웨어는 더 저렴해지고 있습니다. “의도를 기술하는 프롬프트”와 “올바른 실행 가능 출력” 사이의 간극은 줄어들고 있습니다. 1단계는 충분한 소스와 바이너리 쌍으로 훈련된 모델이 올바른 실행 파일을 안정적으로 만들어 낼 수 있을 때 도착할 것입니다. 2단계는 누군가가 이렇게 물을 때 도착할 것입니다. 모델이 이미 바이너리를 생성하고 있다면, 왜 우리는 더 이상 쓰는 일을 하지 않는 종을 위해 설계된 명령어 집합을 겨냥하고 있는가?

어느 시점에 중간 계층은 선택 사항이 됩니다. 그리고 선택 사항인 것은 충분한 시간이 지나면 흔적 기관처럼 남게 됩니다.

이것이 의미하는 것

저는 이 세 접근법이 서로 경쟁한다고 생각하지 않습니다. 이들은 하나의 시간선 위에 놓인 세 지점이고, 그 시간선은 중간 계층이 줄어드는 이야기입니다.

Near term          Medium term           Long term
(now)              (2-5 years)           (5-15 years)

Explicit           Declarative           Post-language
languages          languages             (AI-native targets)
(Vera)             (Haskell + BHC)

Reduce noise  -->  Change the signal --> Remove the layer
in the loop        entirely              entirely

HOW, but           WHAT, verified        Intent to
unambiguous        by types              execution

가까운 시기에는 Vera 같은 명시적인 언어가 생성 공간을 제약하고 기계가 읽을 수 있는 진단을 제공함으로써 AI가 생성한 코드를 더 신뢰할 수 있게 만듭니다. 이것은 오늘 유용합니다. 지금 AI 코딩 파이프라인을 만들고 있고 다음 분기에 출시해야 한다면, 이 접근법은 통합니다.

중기적으로는 Haskell 같은 선언적 언어가, 특히 현대적인 도구와 현대적인 런타임을 갖추었을 때, AI가 생성한 코드를 구성상 올바르게 만듭니다. 타입 시스템이 근본적인 수준에서 무거운 검증 작업을 맡습니다. 살아남는 코드는 절차가 아니라 불변식을 기술하는 코드입니다. 이것이 제가 arcanist.sh로 대비하며 만들고 있는 시대입니다.

장기적으로는 언어가 사라집니다. 먼저 AI가 직접 생성하는 기존 기계어 속으로, 그다음 처음부터 AI 생성을 위해 설계된 새로운 실행 형식 속으로 사라집니다. 70년 동안 소프트웨어 엔지니어링을 규정해 온 중간 계층은 구현 세부 사항이 됩니다.

그 마지막 가능성은 어떤 사람들을 불편하게 합니다. 12월에 처음 그것을 생각했을 때 저도 불편했습니다. 그것은 우리가 아는 프로그래밍이라는 기예, 즉 문법의 유창함, 관용구의 숙달, 우아한 구현을 알아보는 감각이 핵심 기술이라기보다 수동 선반을 다루는 법을 아는 것에 가까워진다는 뜻입니다. 맞는 맥락에서는 가치가 있습니다. 어려운 경우에는 여전히 필요합니다. 하지만 더 이상 대부분의 소프트웨어가 만들어지는 주된 방식은 아닙니다.

이런 일은 전에도 있었습니다. 모든 프로그래머가 어셈블리를 이해하던 시절이 있었습니다. 그러다 C가 그것을 추상화했고, 대부분의 프로그래머는 기계어를 읽지 않게 되었습니다. 그러다 Python과 JavaScript가 C를 추상화했고, 대부분의 프로그래머는 메모리 관리를 생각하지 않게 되었습니다. 매번 이전 계층은 사라지지 않았습니다. 다른 모든 사람이 딛고 선 인프라를 유지하는 전문가들의 영역이 되었습니다. 소스 코드에도 같은 일이 일어날 것입니다. 사라지지 않을 것입니다. 전문화될 것입니다.

저는 지금도 매일 코드를 씁니다. 지금도 타입으로 생각합니다. 지금도 hx와 BHC를 만들고 있습니다. 중기적 미래가 실재하며 동시에 길다고 믿고, Haskell의 강점이 바로 그 미래가 요구하는 것이라고 믿기 때문입니다. 순수 함수, 강한 타입, 증명 가능한 정확성. 이것들은 AI가 구현을 쓰는 세상에서 사치가 아닙니다. 하중을 받치는 구조입니다. 하지만 저는 한쪽 눈을 지평선에 둔 채로 그 일을 합니다. 제가 투자하고 있는 중간 계층이 말 그대로 중간이라는 것을 알기 때문입니다.

Vera를 만든 사람은 아마 비슷한 시기에 제가 본 것과 같은 것을 보았을 것입니다. 그 사람은 첫 번째 접근법을 택했습니다. 저는 두 번째를 택했습니다. 누군가는 결국 세 번째를 만들 것입니다. 그러면 아무도 더 이상 프로그램을 쓰지 않을 때 우리가 “프로그래밍”이라는 말로 무엇을 뜻하는지, 우리 모두 알아내야 할 것입니다.

저는 벌써 그 답이 궁금합니다.

    전체 검색 열기 →