언어 모델 시대의 Clojure
요약
본 글은 생성형 AI 시대에 개발자에게 필요한 핵심 역량으로 고수준 추론 능력을 강조하며, Clojure와 같은 데이터 중심적이고 함수를 선언적으로 조합하는 언어의 가치를 제시합니다. 특히 Clojure가 제공하는 라이브 프로세스(live process) 및 REPL 기반 워크플로우는 대규모 프로젝트에서 피드백 주기 단축과 상태 관리의 용이성을 극대화하여 개발 효율을 높입니다.
핵심 포인트
- AI 시대에는 고수준 추론 능력이 중요하며, 데이터 중심 언어가 유리합니다.
- Clojure의 REPL 기반 라이브 프로세스는 컴파일/재구축 시간을 획기적으로 줄여줍니다.
- 불변성(immutability)과 순수 함수는 에이전트가 코드를 안전하게 테스트하고 변경할 수 있게 합니다.
- 애플리케이션 상태를 재구성하는 과정의 어려움을 해결하여 개발 속도를 높입니다.
생성형 모델 시대에 개발자에게 가장 중요한 기술은 문제의 형태를 인식하고 이를 표현하는 올바른 방법을 선택할 수 있는 능력입니다. 오늘날 관련성이 높은 것은 시스템 내에서 알고리즘, 자료 구조 및 데이터 흐름에 대해 고수준 추론(high level reasoning)을 할 수 있는 능력입니다. 명령형 프로그래밍(Imperative programming)은 언어 모델이 작은 규모의 코드는 상당히 능숙하게 작성하지만, 고수준 설계와 아키텍처에서는 어려움을 겪기 때문에 점차 어셈블리어를 작성하는 것과 유사해지고 있습니다. 따라서 데이터 중심적이며 함수를 선언적으로 조합하고 코드를 문제의 형태에 가깝게 유지하는 Clojure와 같은 더 높은 수준에서 작동하는 언어를 배우는 것이 좋습니다.
LLM은 제가보다 훨씬 빠르게 코드를 생성할 수 있지만, 문제는 생성된 코드가 제가 원하는 대로 작동하는지 어떻게 테스트하느냐입니다. 여러 언어에서 어떤 것을 테스트하기 전에 프로그램을 다시 컴파일해야 하며, 이는 대규모 프로젝트의 경우 상당히 오랜 시간이 걸릴 수 있습니다. 이러한 피드백 주기(feedback cycle)의 길이는 결과적으로 진행 속도를 결정합니다.
또한 애플리케이션 상태를 재구축하는 지루한 현실에 대해서도 이야기할 필요가 있습니다. 이 점은 코드를 직접 작성하지 않아 출력이 본질적으로 덜 의도적일 때 더욱 중요해집니다. Clojure는 그 루프를 무너뜨립니다. 왜냐하면 우리의 워크플로우는 코드가 읽히는 시점, 컴파일되는 시점, 실행되는 시점을 명확하게 구분하지 않기 때문입니다. Clojure는 라이브 프로세스(live process)에서 실행되며, REPL에서 함수를 재정의하고 다시 시작할 필요 없이 새로운 버전이 즉시 적용되도록 할 수 있습니다. 상태는 제자리에 유지되고 그 위에서 작동하는 코드를 변경할 수 있습니다. 실행 중인 프로세스로 접근하여 상태를 검사함으로써 동작을 재현하고 수정 사항을 검증하는 것을 즉시 수행할 수 있습니다.
에이전트는 유사하게 REPL(Read-Eval-Print Loop)에 연결하여 문제를 진단하고 다운타임 없이 코드를 교체할 수 있습니다. 에이전트가 자신이 수행하는 변경 사항에 대한 유용한 피드백을 얻으려면 근본적으로 관측 가능성(observability)이 필요합니다. REPL로 작업한다는 것은 에이전트가 애플리케이션을 컴파일하고 재구축한 후 그 출력을 기록하여 결과를 확인하는 모든 단계를 거칠 필요가 없다는 것을 의미합니다. 이는 작동하는 시스템에 도달하기 위해 반복 횟수를 줄이는 것으로 이어지며, 특히 대규모 코드베이스를 다룰 때 매우 가치가 높아집니다. 시스템의 기능은 재시작 없이 실행 중인 프로세스에 새로운 코드를 로드함에 따라 계속 진화할 수 있습니다.
또 다른 장점은 불변성(immutability)에서 비롯되는데, 이는 프로그램 환경의 운영 컨텍스트를 안정적으로 제어하는 데 도움이 됩니다. 애플리케이션의 대부분의 논리가 순수 함수(pure functions)를 사용하여 작성될 때, 에이전트는 전체 프로그램을 추론할 필요 없이 조각들을 안전하게 고려하고 격리하여 테스트할 수 있습니다. 에이전트는 한 번에 하나의 함수를 작성하고, 모든 변경 사항을 만든 다음 테스트를 실행하여 작동했는지 확인하는 대신 실행 중인 프로그램에서 이를 테스트할 수 있습니다.
이 지점에서 저는 라이브 프로그래밍 환경 옵션이 있다면 컴파일 주기(compile cycle)가 있는 것은 시작조차 할 수 없다고 생각합니다. 고통스러운 것은 단지 컴파일 및 시작 시간이 아닙니다. 더 큰 문제는 매번 원하는 상태를 재구성해야 하는 지루한 필요성입니다. 기능이 제한적인 작은 것을 가지고 있을 때는 괜찮지만, 애플리케이션이 커짐에 따라 상태를 재구축하는 데 상당한 노력이 들 수 있습니다. 사용자 인터페이스의 메뉴를 클릭하거나 서비스 또는 데이터베이스에서 데이터를 처리하기 위해 기다리는 등 여러 작업을 거쳐야 할 수도 있습니다. 애플리케이션을 특정 상태로 만들고 그 컨텍스트 내에서 변경 사항을 적용할 수 있는 것은 질적으로 더 나은 개발 경험입니다.
다음으로 강력한 매크로 시스템이 있습니다. 이 시스템은 언어를 문제 영역에 맞게 조정할 수 있게 해주어 그렇지 않았다면 작성해야 했을 많은 보일러플레이트(boilerplate) 코드를 제거해 줍니다. Clojure에서 이것이 특히 강력한 이유는 호모아이코닉(homoiconic) 구문입니다. 여기서는 코드 자체가 데이터 구조 리터럴을 사용하여 작성됩니다. 논리와 데이터를 표현하는 공통된 문법이 존재하기 때문에, 프로그램은 임의의 코드를 가져와 다른 어떤 데이터 구조를 다루는 것처럼 조작한 다음 평가할 수 있습니다. 이는 새로운 의미론(semantics)을 추가하는 것을 믿을 수 없을 만큼 쉽게 만듭니다. 필요한 모든 것은 코드로부터 템플릿을 만드는 것뿐이기 때문입니다. 매크로는 작성자가 작성한 코드를 받아 실제로 평가되는 형태를 생성하는 함수와 매우 유사하게 작동합니다. 문자열을 출력하는 표현식은 평가될 때 출력을 수행하지만, 그것 또한 println 심볼과 문자열 자체의 리스트에 불과합니다.
S-expression 기반 구문의 주요 장점 중 하나는 인간과 기계 모두에게 읽기 쉽다는 것입니다. 그리고 상태가 단순한 데이터 구조로 사소하게 직렬화될 수 있기 때문에, 에이전트는 REPL에서 덤프하여 언제든지 애플리케이션에서 무슨 일이 일어나고 있는지 검사할 수 있습니다. 이 언어의 데이터 중심적인 특성은 LLM에 자연스럽게 적합합니다. 왜냐하면 이러한 모델은 텍스트로 작동하기 때문에, 걱정해야 할 불투명한 객체 그래프 없이 시스템을 통해 데이터가 어떻게 흐르는지 보는 것이 매우 쉽기 때문입니다.
더 나아가, 모든 함수는 공통된 데이터 구조 세트를 기반으로 작동하여 레고 블록처럼 데이터를 변환하기 위해 이들을 조합할 수 있게 합니다. Clojure 프로그램은 코드가 표준 라이브러리의 함수들로부터 선언적 구성(declarative composition)을 통해 크게 작성되기 때문에 훨씬 더 간결한 경향이 있습니다. 이러한 함수들은 구현 세부 사항을 캡슐화합니다. 그 결과, 코드베이스가 훨씬 짧아지는 경향이 있으며, 반복해야 할 코드가 훨씬 적습니다. 이 간결성은 중요합니다. 왜냐하면 작은 프로그램은 토큰 비용이 적게 들고, 적은 토큰은 컨텍스트에 더 많은 공간을 남겨주어 이 언어를 로컬 모델에게 더욱 친화적으로 만들기 때문입니다.
제 경험상, 코드의 일부를 수정할 때 전체적인 맥락(context)을 갖추지 못한 모델이 실패하는 것은 가장 흔한 오류 사례 중 하나입니다. 간결한 구문(terse syntax)은 더 관련성 높은 코드가 컨텍스트 윈도우에 직접 존재하게 함으로써 이 문제를 직접적으로 해결합니다. 모델은 사용자가 무엇을 하려고 하는지에 대해 훨씬 나은 시야를 얻고 훨씬 더 좋은 결정을 내릴 수 있습니다. 전체 호출 그래프(call graph)가 컨텍스트 윈도우 안에 놓여 있다면, 모델은 모든 조각들이 어떻게 함께 맞물리는지 볼 수 있습니다.
이러한 모든 기능들은 에이전트(agent)가 작업하기에 완벽한 환경을 만듭니다. 표현력이 풍부한 구문(Expressive syntax)은 코드 반복을 줄여줍니다. 매크로(Macros)를 사용하면 반복되는 패턴들을 새로운 도메인 특화 구조물로 접어 넣을 수 있습니다. 코드는 그 자체로 검사하고 변환할 수 있는 구조화된 데이터입니다. 그리고 REPL이 이 모든 것을 하나로 묶어주며, 코드와 함께 진화하는 살아있는 시스템(living system)을 제공합니다.
하지만 Clojure가 전통적으로 실행되는 Java 가상 머신(JVM)에는 몇 가지 단점이 있습니다. 제 경험상, 많은 개발자들이 어느 정도는 JVM 요구 사항에 어려움을 느끼고, 시작 시간, 다소 무거운 런타임(runtime), 그리고 인식되는 부팅 복잡성 때문에 Clojure를 기피하는 경향이 있습니다.
특히 Jolt의 목표 중 하나는 이러한 우려 사항들을 해결함으로써 기존 Clojure 커뮤니티 외부로부터 더 많은 관심을 받는 것입니다. 컴파일러는 단일 바이너리이며, 종속성 관리(dependency management) 및 작업 실행과 같은 모든 도구들이 내장되어 배포됩니다. FFI를 통해 네이티브 생태계와 원활하게 상호 운용되므로, Python과 유사한 방식으로 사용할 수 있습니다. 무엇보다도, 프로그램 배포는 Go처럼 독립적인 바이너리를 빌드하는 것을 포함합니다. 따라서 Jolt는 Clojure를 시도하려는 마지막 큰 마찰 요인을 제거할 수 있을 것입니다.
코드를 작성하는 것은 저렴하지만 검증과 반복(iteration)은 여전히 비싼 시대에, 높은 수준의 선언적 스타일(declarative style)은 에이전트와 인간 모두가 작동하는 코드를 생성하는 데 필요한 것과 정확히 일치합니다. Clojure는 언어 모델의 시대를 담아내는 독특한 방식으로 인해 오늘날 배우기에 훌륭한 언어입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Lobste.rs AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기