Haskell 개발에서 느린 이터레이션을 피하기 위해 REPL 중심 워크플로를 쓰는 방법을 소개한다. GHCi 준비, 스케일링, 작은 함수 설계, 라이브 리로드 등 실전 팁을 정리했다.
Haskell에서의 개발 이터레이션 시간이 왜, 그리고 정말 느린지에 대한 온라인 논의가 있었다. 내겐 느리지 않다. 나는 Haskell 개발을 전부 REPL로 한다. 그 논의 중에 적었던 몇 가지 팁을 여기 정리한다.
프로젝트에서 무언가를 쓰기 전에 맨 먼저 해야 할 일은 REPL, 즉 GHCi에서 내 코드를 불러올 수 있는지 확인하는 것이다. 때로는 특별한 설정 옵션이 필요할 수도 있다. (cabal repl과 stack ghci 덕분에 예전보다 훨씬 쉬워졌다.) 빠를수록 좋다. “시작하고, 실행하고, 끝나는” 식으로만 동작하리라 가정한 프로젝트는 REPL에서 로드하기가 꽤 번거로울 수 있다. 이런 프로젝트는 종종 정리 절차 없이 스레드를 띄우곤 한다. 이런 점에서 REPL은 더 깔끔한 아키텍처를 고민하게 만든다.
프로젝트가 커져도 벽에 부딪히지 않도록, GHCi를 빠르게 쓰는 방법을 익혀라. 바이트코드로 로드하는 것이 오브젝트 코드보다 훨씬 빠르지만, 오브젝트 코드 로드는 캐시가 있어서 100개 모듈짜리 프로젝트라 해도 하나만 다시 로드하면 그것만 로드한다. 필요할 때 이런 동작이 제대로 되는지 확인하라. 설정을 이리저리 만져 보라.
유닛 테스트에 좋은 코드는 REPL에도 좋다. 함수가 자기 상태를 직접 로드하게 하지 말고, 상태를 인자로 받는 작은 함수들(즉 의존성 주입)을 작성하라. 그러면 REPL에서 실행하기도, 테스트 스위트에 쓰기도 쉽다. 그냥 직접 호출할 수 없는 함수라면 의심의 눈초리로 보라.
코드를 작성하면서, 최종 사용 지점에 바로 붙여 넣어 쓰기보다, 해당 함수가 받을 전형적인 인자들로 REPL에서 먼저 시험하라. 사소한 "접착(glue)" 함수라면 건너뛰어도 되지만, 사소하지 않은 함수에는 큰 도움이 된다.
테스트와 REPL 실행을 위해 유용한 셋업/티어다운 코드를 만들어 두라. 예를 들어 어떤 함수가 데이터베이스와 애플리케이션 설정이 있어야만 동작한다면, 개발용 기본 설정과 데이터베이스 연결을 자동으로 편하게 얻어와 어떤 액션을 실행해 주는 도우미 함수를 만들어라.
REPL에서 데이터를 들여다볼 수 있도록, 데이터 타입에 Show 인스턴스를 반드시 포함시켜라. Show는 개발용 인스턴스로 취급하라. 이것은 개발자인 너를 위한 것이지, “진짜” 직렬화나 “사용자 친화적” 메시지에 쓰지 마라. 들여다보기 어려운 자료구조는 기피하는 습관을 들여라.
:reload 같은 기법을 활용하라. 예컨대 내가 hindent를 작업할 때는 REPL에서 HIndent.test chrisDone "x = 1" 같은 식으로 스타일을 시험하고, Emacs REPL에서 Haskell로 예쁘게 포매팅된 출력을 확인한다. 하지만 내가 실제로 작업하는 모듈은 HIndent.Style.ChrisDone이다. 그래서 먼저 :load HIndent를 하고, 이후에는 :reload로 .ChrisDone 변경분만 다시 로드해 HIndent 환경을 다시 얻는다.
홈 디렉터리 ~/와 GHCi를 실행하는 프로젝트 디렉터리 둘 다에 둘 수 있는 .ghci 파일을 알아두라. :set을 사용해 일반 GHC 옵션(패키지 -package foo, 확장 -XFoo)과 특별한 include 디렉터리(-ifoo)를 설정할 수 있다.
가능하다면 라이브 리로딩 같은 요령을 고려하라. 나는 IRC 서버를 하나 썼는데 REPL에서 실행해 둔 채로 코드를 리로드하고, 상태를 잃지 않고 핸들러 함수를 업데이트할 수 있었다. foreign-store를 쓰면 프로그램 상태 같은 것을 IORef나 MVar에 담아 노출할 수 있다.
이건 어디까지나 트릭이니 프로덕션에는 쓰지 마라. 하지만 리스프식 이미지 개발에 우리가 닿을 수 있는 것 중 가장 가까운 편이다.
Haskell에는 소규모의 REPL 문화가 있다는 건 행운이지만, 완전히 “몰입(buy in)”했을 때 가능한 것이 무엇인지 제대로 알려면 Lisp나 Smalltalk을 경험해 봐야 한다. 많은 Haskeller들이 C++ 배경이라 “프로그램 중지 → 파일 수정 → 컴파일러 재실행 → 전체 프로그램 재실행” 사이클에 익숙하고, REPL 문화에 큰 인식이나 관심이 없다. 만약 당신이 그렇다면 위의 내용이 자연스럽지는 않겠지만, 한번 시도해 보라.