2008년부터 2024년까지 HJScript, Ur, Opa, GHCJS, threepenny-gui, UHC, Elm, Yesod, Haste, Fay, Sunroof, PureScript, React, Reflex, Halogen, Htmx 등을 거치며 하스켈에서 클라이언트 사이드 웹을 시도했던 여정을 개인 경험을 바탕으로 정리한 회고.
이 10년대(2010) 초반에, 몇몇 하스켈러들이 클라이언트 사이드 웹 프로그래밍을 어떻게 하면 가장 잘할 수 있을지 탐구하고 있었다. 우리는 자바스크립트를 쓰고 싶지 않았다. 그렇게 하지 않기 위해 시도했던 기법이 놀랄 만큼 많았다. 학계와 산업계 양쪽에서 다양한 시도가 있었다. 여기서는 이 문제 영역에서 내가 직접 겪은 경험의 역사를 정리한다.
2008년, Joel Bjornson과 Niklas Broberg가 자바스크립트를 작성하기 위한 하스켈 EDSL인 HJScript를 발표했다. GADT를 통해 타입이 있는 JS를 표현할 수 있었다. 나는 2010년에 제한적인 DOM 조작을 위해 이를 프로젝트에 사용했다. 예컨대 jquery에 대한 래퍼를 작성했다. 하스켈로 쓸 수 있는 점은 좋았지만, 동시에 두 언어(게다가 자바스크립트의 의미론을 그대로 따르는)를 함께 써야 한다는 정신적 부담이 컸다. 결국은 다시 평범한 자바스크립트로 돌아갔다.
2010년 전후로 Adam Chlipala가 행(row) 타입을 갖춘 급진적인 웹 개발 언어 Ur를 발표했다. 이 언어는 꽤 투명하게 네이티브 오브젝트 코드와 자바스크립트로 모두 컴파일되며, HTML과 SQL 문법을 언어 안에 내장한다. 나는 코드의 단순함과 정확성에 깊은 인상을 받았지만, 메타프로그래밍이 포함된 일부 코드는 무섭게 느껴졌다. 행 타입 문서를 읽고는 솔직히 겁이 났다. 예제를 몇 개 시도해 본 뒤에는 다시 돌아오지 않았다.1 지금도 나는 이 아키텍처 자체에는 관심이 있다.
2011년 어느 시점에 Opa가 등장했지만, 또 다른 서버 사이드 언어를 배우고 싶어 하는 사람은 거의 없었다. 내가 아는 한, 이것을 프로덕션에 쓴 사람은 없다.
2011년 8월, 나는 GHCJS를 실험했고, 그 메모를 나중에 하스켈 위키에 The JavaScript Problem이라는 이름으로 옮겼다. 당시 단순한 GHCJS 실험에서도 버그와 런타임 문제를 겪었다. 그래서 크게 실망하고 선택지에서 대부분 제외했다.
2011년 12월, 나는 하스켈 웹 서비스에서 웹 페이지를 제어하는 수단인 ji를 고안했고, 이후 threepenny-gui로 이름이 바뀌어 지금도 Heinrich Apfelmus가 유지하고 있다. 이는 꽤 강력하다는 게 드러났다. 나는 웹 페이지에서 사람들이 채팅할 수 있는 IRC 비슷한 앱을 한 페이지 분량의 코드로 작성했다. 하지만 결국 주류 웹 개발에 적합하진 않았다. Michael Snoyman이 지적했듯 서버 사이드 비용이 높고, 여러 서버로 스케일링되지 않았다. 결론적으로 threepenny-gui는 (Electron 같은) 크로스 플랫폼 데스크톱 프로그램을 고려할 때 훌륭한 라이브러리다.
2012년 1월, 나는 UHC를 실험했다. 경험은 GHCJS와 비슷했다. 기초적인 실험을 해봤지만, 함께 딸려오는 무거운 런타임은 매력적이지 않았다. 애플리케이션을 만드는 것과 별개로 시간의 절반을 그것 자체를 유지보수하는 데 쓸 것 같았다.
2012년 3월, Evan Czaplicki가 Elm에 관한 자신의 학위 논문이 완성되었다고 발표했다. FRP에 무게를 둔 점 때문에 나(그리고 아마 당시 다른 사람들도)는 멀어졌다. FRP는 미숙하고, 실험적이며, 실용적이지 않다고 여겨졌기 때문이다. 실제로 2016년에 Evan은 A Farewell to FRP를 썼다. Elm은 한때 FRP가 전부였지만, 더 이상 그렇지 않다. “나는 Elm이 결코 FRP에 관한 것이 아니었다고 주장할 수 있다,”고 Evan은 쓴다. 이것이 Elm의 성장하는 성공에 기여했을 것이라고 본다. 오늘날 나의 선택지는 Elm이 아니다. 타입클래스가 없기 때문이다.
2012년 4월, Michael Snoyman은 동적 페이지 변화를 위해 제한된 자바스크립트를 투명하게 생성하는 고수준 하스켈 코드 작성 실험을 아주 흥미롭게 진행했다(그리고 여기). 지금까지도 나는 이 경로를 탐구하는 데 관심이 있다.
2012년 5월, Anton Ekblad가 Haste 컴파일러를 발표했다. 이는 GHC 코어를 거치는, GHCJS와 유사한 하스켈→자바스크립트 컴파일러지만, 브라우저에서 GHC 런타임을 구현하려는 시도는 덜했다. 그의 박사 논문_Functional EDSLs for Web Applications_의 동인이 되었다. 우리는 실제 프로젝트에서 제대로 써보지는 못했다.
2012년 9월, 나는 Fay 컴파일러를 작성했다. 하스켈의 서브셋을 컴파일하는 컴파일러였다. Elm과 Roy에서 영감을 받았다. 아이디어는 GHC 컴파일러를 타입체킹에 재사용하고, 코드 생성은 타입 정보 없이 haskell-src-exts로 따로 하자는 것이었다. 그 결과 타입클래스를 지원하지 않았고, Hackage의 거의 어떤 패키지도 컴파일하지 못했다. 하지만 커뮤니티와 FP Complete에서 쓰일 만큼 충분히 유용하고 단순했다. 우리는 웹 기반 IDE에 이것을 사용했고, 대략 1만5천 줄의 Fay 코드가 있었다.
추가 맥락을 덧붙이자면, 그 당시 나는 이렇게 썼다:
나를 포함해 모두가 이 문제에 접근하던 방식은 오래전부터 이랬다. 어쩔 수 없으니, 그냥 구글이 네이티브 클라이언트 프로젝트를 완성하길 기다리고, 그것이 시장을 뒤흔들길 바라자.
2013년 3월, Andrew Gill과 Jan Bracker가 sunroof 프로젝트를 발표했다. HJScript의 확장판 같은 것으로, 당시에는 꽤 설득력 있게 보였던 사람들도 있었다. 프로덕션에서 쓴 사람이 있는지는 모르겠다. 내 생각에 이것이 하스켈에서 자바스크립트 DSL을 향한 마지막 불씨였고, 결국 사그라들었다.
2013년 10월, Phil Freeman이 PureScript를 발표했다. 엄격하고 순수하며, 행 다형성을 갖춘 하스켈 유사 언어로, 런타임 없이 JS로 컴파일된다. 흥미로운 개념으로 보였지만, 몇 년이 지나서야 PureScript가 실용적인 언어로서 다시 내 레이더에 들어왔다.
2014년 말경, FP Complete에서 우리는 GHCJS를 다시 시도할 준비가 되었다. Luite Stegeman이 그것을 열심히 다듬고 되살리고 있었다. 2014년 8월, Simon Meier가 blaze-react를 발표했다. 이는 Elm 스타일의 합타입 이벤트 처리를 갖춘 GHCJS 기반 React.js 바인딩이었다. 이로 인해, 적어도 내 관점에서는, 관심이 다시 불붙었다. 다만 우리는 Elm 아키텍처가 확장성 면에서 의구심을 가졌다. Fay는 공식적으로 그림에서 사라졌다.
2015년 4월, 우리는 ghcjs-react라는 비슷한 패키지를 만들어, 렌즈/커서로 상태를 다루는 Om 스타일 없이 React를 구현했다(비슷한 시기에 Redux가 등장해 유사한 일을 했다). 나는 이 라이브러리를 기반으로 실험적 도구 stackage-view를 작성했다. 이것이 GHCJS와 우리의 React 바인딩을 평가하는 리트머스 시험지가 될 거라 생각했다. 어색한 면이 있었지만, 동작할 때는 꽤 잘 동작했다.
덧붙이자면, 우리는 따라가는 속도가 느렸다. React.js는 2013년 5월에 출시되었고, 우리가 그것을 UI를 작성하는 좋은 방식으로 받아들인 건 2015년쯤이었다. 부분적으로는 Clojure 라이브러리 Om 덕분이기도 하다.
역시 2015년 4월, Ryan Trinkle이 새 라이브러리 Reflex를 발표하며 매우 설득력 있는 발표를 했다. 나는 그때 이렇게 썼다:
이 영상은 꽤 이해하기 쉽다. 내가 본 FRP 데모 중 처음으로, 이해 못 하는 용어들에 얽매이지 않고, 작은 컴포넌트로부터 차근차근 쌓아가면서도 비현실적인 카운터나 슬라이더로 지루하게 하지 않으며, 실제 제품이 이것으로 작성돼 있다. 10/10 발표, 관심 등록.
2016년 1월, FP Complete 팀 일부가 Reflex를 배우고 큰 고객 파일럿 프로젝트에 사용하기 시작했다. 경험 요약: 하스켈을 배울 때와 같은 방식으로 머리를 비트는 동시에 눈이 트이는 느낌. 어떤 것들은 표현이 어색하지만, 의심할 여지 없이 합성 가능하고 재사용 가능하다. GHCJS가 무겁고 느리고 깨지기 쉽다는 불평도 나왔다.
2015년 1월, Slam Data가 PureScript용 Halogen 라이브러리를 시작했다.
2015년과 2016년에 걸쳐, Tim Dysinger는 FP Complete 내부 논의에서 PureScript의 옹호자로 점점 목소리를 높였다.
2016년 10월, 나는 PureScript와 React.js 바인딩으로 FP Complete의 내부 데모 UI를 만들고 있었다. 상당히 즐거웠다. 우리는 PureScript 대 다른 기술에 대한 엔지니어링 미팅을 가졌다.
2016년 12월, Michael Snoyman이 Slack에서 이렇게 말했다:
PureScript는 모든 걸 제대로 하고 있는 것처럼 보인다.
2017년 4월, Michael Snoyman이 자신의 원타임패드 토이 앱의 PureScript + Halogen 버전을 작성했다. 그때 나는 Halogen에 관심이 없었다. 복잡해 보였고, 나는 여전히 React를 즐기고 있었다.
2018년 8월, FP Complete에서 암호화폐 클라이언트를 위한 UI를 PureScript로 만들기로 결정되었다. 나는 만족스러웠다. Halogen을 써보기로 했다. 초기에는 고통이 있었지만, 결국 사랑하게 되었다.
2018년 11월, Rust+WASM이 새로운 경쟁자가 될 수 있는지 잠깐 논의했다. 특히 모바일을 포함해 모든 브라우저에서 WASM이 지원될 때까지 기다려야 할 것이다.
2018년 12월, 우리는 프런트엔드 개발의 현황에 대한 개발자 회의를 했다. 회의록은 길고 자세했지만, 요약하면: 우리는 GHCJS에 대해 편하지 않았고, Reflex는 훌륭하지만 비용이 크다(새로운 것이 두 가지라는 예산 문제). Halogen은 Elm보다 더 나은 아키텍처다. PureScript와 Halogen이 최선의 선택으로 나왔다. 함께 고려한 다른 것들: ClojureScript, TypeScript, Rust, ReasonML, Elm, OCaml.
2019년 기준으로, 나는 개인적으로 여전히 PureScript와 Halogen을 사용하고 있었다.
2023년에 나는 Artificial Labs 팀에서 Haskell과 결합한 Htmx를 시도해 보자고 추진했다. 지금까지 경험은 좋다. 우리는 하스켈을 쓰는 것을 좋아한다. 하지만 Htmx에는 단점도 있다. 상속은 나쁜 아이디어이고, 요청 큐는 기본값이 나쁘며 관리가 어렵고, htmx의 직관(특히 요청 큐 관련)은 morphdom과 잘 맞지 않는다. 이에 대해 전체 글을 여기에 썼다.
2024년 현재, 나는 여전히 Halogen이 모든 면에서 React보다 낫다고 생각하며, 내가 경험해 본 SPA 작성 방식 중 Halogen이 가장 좋다고 본다. 다만 2022년 이후로는 사용하지 않았다.