1P by GN⁺ 4시간전 | ★ favorite | 댓글 1개
  • ClojureScript 1.12.145^:async 힌트를 붙인 함수를 JavaScript async function으로 출력하도록 컴파일러가 변경됨
  • awaitPromise 값을 기다리는 ClojureScript 함수를 작성할 수 있어 JavaScript 상호운용성이 개선됨
  • 테스트에도 ^:async 를 사용할 수 있으며, await로 비동기 함수 호출 결과 검증이 가능함
  • 최근 Clojure 설문에서 async functions 지원이 JavaScript 상호운용성 관련 ClojureScript 개선 요구 중 가장 높은 비중을 차지함
  • 최신 브라우저 API와 인기 라이브러리를 다루는 일반적인 경우 추가 의존성을 도입할 필요가 줄어들며, 전체 변경 목록은 ClojureScript changelog의 1.12.145 항목에서 확인 가능함

^:asyncawait 사용

  • ClojureScript 1.12.145는 ^:async 힌트를 붙인 함수를 JavaScript async function으로 출력하도록 컴파일러가 변경됨
  • ClojureScript가 ECMAScript 2016을 대상으로 하게 되면서 JavaScript 상호운용성 개선 영역을 신중히 선택할 수 있게 됨
  • await를 사용해 Promise 값을 기다리는 ClojureScript 함수 작성이 가능해짐
    (refer-global :only '[Promise])
    
    (defn ^:async foo [n]
      (let [x (await (Promise/resolve 10))
            y (let [y (await (Promise/resolve 20))]
                (inc y))
            ;; not async
            f (fn [] 20)]
        (+ n x y (f))))
    
  • 테스트에도 ^:async를 사용할 수 있으며, await로 비동기 함수 호출 결과를 검증할 수 있음
    (deftest ^:async defn-test
      (try
        (let [v (await (foo 10))]
          (is (= 61 v)))
        (let [v (await (apply foo [10]))]
          (is (= 61 v)))
        (catch :default _ (is false))))
    

배경과 변경 목록

  • 최근 Clojure 설문에서 async functions 지원이 JavaScript 상호운용성 관련 ClojureScript 개선 요구 중 가장 높은 비중을 차지함
  • 이번 개선으로 최신 브라우저 API와 인기 라이브러리를 다루는 일반적인 경우 추가 의존성을 도입할 필요가 줄어듦
  • 전체 수정, 변경, 개선 목록은 ClojureScript changelog의 1.12.145 항목에서 확인 가능함
  • ClojureScript 1.12.145에는 커뮤니티 구성원 Michiel Borkent가 기여함
Hacker News 의견들
  • borkdude가 이 스레드를 올렸고, 이번 릴리스의 기여자로도 올라와 있는 걸 봤음
    오래전부터 async/await 지원 반대 논리는 대체로 두 가지였음: CLJS 컴파일러 전반에 깊은 변경이 필요하다는 점과, Promesa 같은 라이브러리의 매크로가 비슷한 편의성을 제공한다는 점
    그 외에도 core.async를 쓰면 된다거나, 표현식 지향 언어는 async/await와 잘 맞지 않는다는 주장도 있었지만, 포럼에서 반복되던 주류 논거라기보다는 개인별 견해에 가까웠음
    Clojurians Slack에서 borkdude는 지원 추가가 비현실적이라고 확신하지 않는다고 한 적이 있는데, 결국 시간을 들여 구현해낸 듯해서 정말 고마움

    • Borkdude가 먼저 자신의 ClojureScript 대체 구현인 Squint에 async/await를 구현해서 가능성을 보였고, 그때 얻은 내용을 핵심 CLJS 컴파일러로 가져온 것 같음
  • 재미있는 사실은 ClojureScript가 JavaScript 자체에 async/await가 들어오기 훨씬 전부터 core.async 라이브러리로 비동기 패러다임을 지원했다는 것임
    이 릴리스의 가치를 깎아내리려는 건 전혀 아니고, 의존성에 라이브러리 하나를 추가하는 것만으로 호스트 언어에 아직 없는 새 언어 기능을 쓸 수 있다는 점이 멋지다는 얘기임. Clojure는 굉장함

    • 확실히 그랬음. 많이 써봤고 동작도 잘했으며, 약간의 특이점은 있었지만 10년 넘게 사실상 async/await를 가지고 있었음
      David Nolen의 발표를 보고 알게 된 것 같음
      이후에는 프런트엔드에서 JavaScript를 최소한으로 쓰는 쪽으로 옮겼고, SSE는 단방향이라서 그 점이 아름다움. 요즘 여러 언어권 개발자들이 SSE에 관심을 갖는 걸 보니 반가움
      David Nolen의 최근 발표 “A ClojureScript Survival Kit”도 좋았음: https://youtu.be/BeE00vGC36E
      David “Swannodette” Nolen이 ClojureScript와 core.async 초창기부터 해온 작업에는 아무리 감사해도 부족함. 이 발표에서 특히 놀라운 건, 그가 ClojureScript를 버리고 서버 쪽의 순수 Clojure와 서버 전송 이벤트, 아주 약간의 JavaScript만 쓰는 방향에도 실제로 기대감을 보인다는 점임
      실제 데모는 26:30쯤 시작함. 클라이언트에서 돌아가는 웹앱의 리소스 사용량을 보여준 뒤, 같은 웹앱을 서버에서 실행하고 SSE로 클라이언트에 단방향으로 밀어주는 모습을 보여주는데, 리소스 사용량이 거의 0에 가까워져서 꽤 강렬함
      모든 경우에 맞지는 않겠지만, 최소한의 DOM 변형 라이브러리를 쓰니 웹앱과 상태를 추론하기가 더 쉬워졌음. 예전에는 Clojure용 REPL과 ClojureScript용 REPL을 둘 다 띄우고, 양방향 트래픽과 재현하기 어려운 상태를 많이 다뤄야 했는데, 지금은 훨씬 빠르고 재현도 쉬움
    • 맞지만 2026년에 core.async를 피할 이유도 많음
      JavaScript 산출물이 커지고, 내재된 오류 모델이 없으며, 문제가 생기면 읽고 디버깅하기 어려운 상태 기계 코드로 변환됨
      게다가 go 매크로는 자기 S-표현식 바깥의 코드를 변환할 수 없어서 함수가 지나치게 커지도록 유도함
      어떤 Cognitect 사람이 말했듯이 “core.async는 아름다운 헛소리”임
  • 최근 갑자기 소셜에서 Clojure/ClojureScript가 더 자주 보이는 게 의외임
    2012년쯤 몇 년간 업무에서 썼지만, 다른 많은 사람들처럼 JVM을 떠나 타입 있는 함수형 언어로 옮겼음
    요즘 관심이 늘어난 건 에이전트형 코딩 때문일까? 타입 검사도 없고 잘못된 문법 오류나 예약어도 적어서 코드 훑기가 더 빠른 걸까? S-표현식의 부활이 오는 건가 싶음

    • 개인적으로는 타입 있는 함수형 언어에서 ClojureScript로, 그리고 약 10년 전 Clojure로 옮겼음
      내가 아는 진지한 Clojure 코드베이스들은 테스트 스위트에 많이 투자하므로, AI에게 테스트 스위트를 가장 효과적으로 쓰는 방법을 알려주는 기술만 추가하면 꽤 잘 달리게 할 수 있음
      동료 중에는 에이전트가 REPL과 상호작용하게 하는 사람들도 있고, 매번 시작 비용을 내지 않으니 더 빠르다고 함. 나는 게을러서 거기까지는 안 했지만 지금도 충분히 빠름
      Clojure는 걸리적거리는 요소가 적음. falsenil을 제외하면 모두 참이고, 연산자 우선순위 표가 없으며, 핵심 언어가 불변·영속 자료구조를 기본으로 지원함
      모든 것이 표현식이고, 연산자와 표현식이 뒤섞인 구조가 아님. map, reduce, filter는 내장되어 있고 일반 코드에서 당연히 쓰임
      10년 전에 쓴 Clojure 코드는 오늘도 대체로 동작할 가능성이 높고, 생태계와 언어 설계자들은 코드를 깨뜨리는 일을 금기처럼 다룸
      써본 언어 중 아이디어를 표현하는 데 가장 자유롭고 두통이 가장 적었음. 사실상의 역방향 디버거인 Flowstorm도 프로그래밍의 꿈 같은 도구임
      만족스럽게 지내고 싶다면 참 좋은 언어임. 반대로 사용자 대부분이 그걸 당연하게 여겨서 많이 떠들지 않는 편임
      상업적으로 Clojure를 쓰는 프로그래머 중에는 언어를 잘 이해하지 못해 그다지 행복하지 않은 사람도 많음. 본인이 원해서 선택한 게 아니거나 아직 준비가 안 된 경우가 많고, Clojure를 쓰기 전 다른 언어에서 싫었던 점들을 10년쯤 겪어봤어야 했다고 봄
      Clojure 창시자 Rich Hickey의 소프트웨어 관련 영상들이 유명하고 영향력 있긴 하지만, 동료들이 그걸 봤거나 신경 쓴다는 뜻은 아님
    • 최근 관심 증가는 Clojure 다큐멘터리 공개 영향이 큰 것 같음: https://clojure.org/about/documentary
    • 에이전트형 코딩과 잘 맞는 또 다른 기능은 REPL 주도 개발임. 이론적으로 지원 가능한 다른 언어들에서 왜 이 접근이 더 널리 퍼지지 않았는지 모르겠음
    • 여러 언어에서 에이전트형 코딩을 해봤는데, 타입 있는 언어가 훨씬 잘 맞음. 에이전트가 환각으로 만든 오류를 타입 시스템이 사실상 교정해주기 때문이고, 특히 큰 리팩터링에서 그렇음
      큰 규모의 타입 없는 Python 코드베이스를 AI와 다루는 건 힘들었음. 테스트로 덮이지 않은 부분은 망가지지 않았는지 확인하는 작업이 너무 지루함
      타입 시스템이 강할수록 더 좋음. 또한 AI 모델은 코드로 학습되므로 언어가 더 대중적일수록 성능도 더 좋을 가능성이 큼. ClojureScript는 좋지만 주류 언어는 아니라서 JavaScript보다 AI 성능이 떨어질 거라고 봄
      결국 AI를 염두에 둔다면 타입 있는 언어를 고르거나, 동적 언어라도 타입 힌트가 있는 쪽을 고르는 게 낫다
    • 다큐멘터리가 나왔거나 발표됐던 걸로 기억하는데, 그 영향일 수도 있음
  • 이건 정말 큼. Jank가 발표된 이후 Clojure 생태계에서 이렇게 기대된 적이 없었음

  • 프런트엔드에서 JavaScript 대안이 obscure한 수준을 넘어 실제로 자리 잡았으면 좋겠음
    ClojureScript 같은 걸 써보고 싶지만, 개인 사이드 프로젝트 말고 어디에 쓸 수 있을지 상상하기가 어렵다. 이미 백엔드가 Clojure인 조직이라면 도입이 더 쉬울지도 모르겠음

    • 주류 언어가 아니라 동료들이 모를까 봐 걱정하는 건지, 아니면 언어 자체가 버려지거나 나쁘거나 할까 봐 걱정하는 건지 궁금함
      프로덕션에서는 안 써봤지만, 사이드 프로젝트 몇 개와 가족용 물건은 배포해봤음. ClojureScript의 React 래퍼인 Reagent는 솔직히 React 자체보다 더 말이 된다고 느꼈음
      Hiccup으로 HTML을 만들고, 컴포넌트는 Hiccup DSL 안의 함수일 뿐인데 이 DSL도 사실상 리스트라서 결과가 아주 깔끔함. 정적인 것은 정적으로 보이고, 동적인 것은 분명히 동적으로 보이며, 일반 React보다 마법이 훨씬 적게 느껴졌음
      안 좋게 느낀 건 NPM에서 찾은 비함수형 컴포넌트를 쓰려 할 때였음. 치명적이진 않지만 코드가 못생겨짐. 래퍼로 고칠 수는 있었지만, 일부 JS 라이브러리는 cljs에서 기본 상태가 굉장히 지저분함
    • 겁낼 필요 없음, 정말 좋음. 10년째 복잡한 앱을 고도로 최적화된 클라이언트 코드로 컴파일하는 데 쓰고 있고, obscure하다고 부르지는 않겠음
      커뮤니티도 매우 친절하고 성숙함
    • 상상만 하지 말고 작게 시작하면 됨. 팀에서 쓰는 bash 스크립트가 있다면 Babashka로 다시 써보면 됨
      먼저 개인 스크립트부터 바꿔보고 감을 익힌 뒤 장점을 느껴보는 게 좋음. 모든 경우에 더 나은 건 아니지만, 나중에 사람들이 조언을 구하러 올 수 있으니 본인이 충분히 확신해야 함
      낯선 기술을 들여올 때는 덜 중요한 것을 골라 다시 쓰고 그냥 두는 전략이 좋음. 문제가 되면 되돌리기 쉽고, 사람들이 좋아하기 시작하면 조금씩 늘리면 됨
      예전에 .NET 조직에 F#을 몰래 들여올 때도 덜 중요한 테스트부터 F#으로 쓰기 시작했음
    • Gleam을 써보면 좋음. 프로덕션에서 아주 만족스럽게 쓰고 있음
      https://blisswriter.app/
      https://blog.nestful.app/p/how-we-dropped-vue-for-gleam-and
    • https://hypermedia.systems/를 읽고 나서는 최고의 프런트엔드는 프런트엔드가 없는 것이라는 결론에 도달했음
  • cljs를 오래 따라가지 않았지만, 원래는 “JavaScript 위의 Clojure” 정도로 소개됐던 걸로 기억함. 적어도 Rich가 처음에는 그렇게 설명했던 것 같음
    최대한 또 하나의 런타임에 가깝게 만들려는 의도라고 이해했음
    그런데 이번 변경은 cljs에만 있는 기능을 추가하는 것처럼 보이고, await가 이미 clojure.core의 키워드라서 Clojure 자체와도 충돌함
    두 구현이 시간이 지나며 갈라진 건지, 아니면 이 기능이 사용자들에게 그 차이를 감수할 만큼 중요했던 건지 궁금함

  • 추가 라이브러리를 넣지 않고 JavaScript 상호운용성을 다룰 수 있다는 점에서 중요함
    오래 빠져 있던 기능이라 이번 릴리스가 꽤 반가움

  • async/await 함수를 CSP로 감싸는 편이 더 나은 처리 방식 같음. Clojure에는 이미 더 좋은 패턴이 있었음

    • 이번 릴리스는 호스트 언어의 원시 기능을 ClojureScript에 노출하는 것에 관한 것임
      core.async가 사라지는 건 아니고, async/await가 Promise 기반 구현보다 더 잘 맞는다면 core.async의 .cljs 부분도 업데이트될 수 있음
    • 여전히 그렇게 할 수 있음. ClojureScript 세계에서는 이미 여러 해 동안 가능했고, 결국 그것들은 Promise일 뿐임
      이번 새 함수 힌트가 들어와도 그 방식이 사라지지는 않을 것 같음
      https://clojurescript.org/guides/promise-interop#using-promi...
  • 이걸 어떻게 받아들여야 할지 잘 모르겠음. core.async의 요지 중 하나가 이런 것들을 모두 채널로 밀어 넣는 것 아니었나 싶음
    JavaScript식 async 키워드를 갖는 게 업그레이드인지 확신이 안 듦

    • 이건 core.async 같은 추가 의존성을 가져오지 않고 JS 기능을 쓰기 위한 것임
      반드시 쓸 필요는 없고, 여전히 core.async도 쓸 수 있음. 최근 ClojureScript 설문에서 가장 많이 요청된 기능이기도 했음