저자와 freshcodeit의 허락을 받은 번역입니다. 원글과 차이가 있을 수 있습니다.
클로저는 리습(Lisp)을 기반으로 한 함수형 언어입니다. 번역한 글에서는 클로저의 탄생배경과 특징, 학습 방법이 구체적으로 잘 소개되어 있습니다. 그린랩스의 주 언어 중 하나인 클로저의 매력을 체험해보시죠!
1. 클로저의 역사
1.1 리치 히키
클로저는 리치 히키 (Rich Hickey)가 만들었습니다. 클로저 커뮤니티에선 이미 숭배받는 괴짜 프로그래머로, 뉴욕 대학에서 C++를 가르쳤고, 이후 현실 프로세스와 데이터를 정형화하는 다양한 시스템을 개발했습니다. 2005년 안식년을 가지며 개인 프로젝트를 시작했고, 2년 뒤에 클로저 의 첫 번째 버전이 탄생했습니다. 오늘날 리치 히키와 그가 CTO로 있는 회사 코그니텍(Cognitect)은 클로저를 위한 상업적 지원을 제공하고 있습니다.
1.2 클로저를 쓰는 이유
리치 히키는 클로저를 만든 이유에 대해 다음과 같이 대답했습니다.
"왜 굳이 또 언어를 만들었냐고요? 저는 기본적으로
- 함수형 프로그래밍을 위한
- 리습(Lisp)이
- 이미 갖춰진 플랫폼과 상생하면서
- 동시성 문제를 잘 해결할 수 있기를
원했기 때문입니다."
더욱 자세한 스토리는 언어의 창시자가 직접 작성한 "클로저의 역사"라는 기사에서 확인할 수 있습니다.
클로저는 어쩌면 당신의 기대만큼 새롭고 독창적인 기능들이 많이 있지는 않을 것입니다. 당연히 자바와 비교하면 꽤나 실험적인 언어지만, 애초에 리치 히키는 이미 많이 검증된 아이디어와 컨셉을 기반으로 언어를 설계했습니다.
클로저는 리습의 방언이고, 람다 대수(Lambda calculus)는 일급, 고차함수를 다루는 리습의 핵심 개념입니다. 다른 말로, 함수가 값처럼 취급되어 인자로도, 리턴 값으로도 쓰일 수 있다는 얘깁니다.
리습은 '코드는 데이터다'라는 아이디어에서 출발했습니다. 리습의 기본 코드 단위는 아래와 같은 s-표현(symbolic expression)입니다.
(A (B 3) (C D) (()))
S-표현은 리스트와 값들로 구성되어 있는 리스트입니다. 특징으로는 전위 표현(prefix notation)을 사용하기 때문에 첫번째 인자는 함수 혹은 연산자이며, 그 뒤에 오는 값들은 인자로 취급됩니다. (+ 1 2 3) 은 + 연산자를 1 2 3 에 적용하게 되어 6 이라는 결과가 나옵니다.
리습으로 짠 프로그램은 그 자신의 추상 구문 트리(Abstract syntax tree)와 동일합니다. 이 특징은 다른 말로 동형성(homoiconicity)이라고 하는데, 이런 특징은 프로그램이 실행 중일 때도 쉽게 수정이 가능하게 하는 (modifying on the fly) 메타 프로그래밍의 새로운 장을 열어줍니다.
클로저는 리습을 현실 세계의 문제를 더 잘 다룰 수 있도록 혁신한 버전입니다. 예를 들어서, 리스트만 있던 리습에 비해 클로저는 맵, 벡터, 셋 과 같은 데이터 자료형이 추가되었습니다.
첫 릴리즈 후 얼마 지나지 않아, 리습에 매크로라는 개념이 추가됐습니다. 리습 프로그램을 해석하는 과정에 매크로 확장(macro-expansion)이 추가되어, 프로그래머들이 리습을 확장해 사용하는 것이 전보다 훨씬 용이해졌습니다. 하지만, 이러한 용이함은 동시에 발목을 잡기도 했습니다. (리습의 저주) 시간이 흐름에 따라 리습 커뮤니티는 마구 확장됨과 동시에 파편화되었고, 잉여스러운 구현들이 넘쳤으며, 통일된 표준을 찾기 힘들었습니다.
리습의 탄생 이후, 커먼 리습을 포함한 여러 방언이 나왔습니다. 하지만 현대 스크립트 언어인 파이썬이나 루비만큼의 인기를 얻진 못했죠. 그래서 클로저의 목표중 하나는 리습의 대중화였습니다. 이 목표는 JVM 플랫폼과 커뮤니티의 도움으로 잘 현실화 되고 있습니다.
1.3 통계자료
클로저 2020의 설문 조사에 따르면, 작년 한 해 기업용, 상업용 애플리케이션 개발에 점점 더 많이 사용되고 있음을 알 수 있습니다.
클로저는 꾸준히 새로운 사용자들을 유입하고 있습니다. 2020년에는 응답자 중 15.78%가 처음 사용하기 시작했다고 했습니다. 지금 클로저를 배우는 것이 시기적절하고 유망한 트렌드로 보입니다.
더 자세한 내용과 인사이트는 클로저에 관한 지난 기사에서 찾아보실 수 있습니다.
2. 클로저를 배우는 세 가지 단계
루스 올슨(Russ Olsen)가 쓴 "Clojure Applied"의 서문을 보면, 대부분의 사람들은 클로저를 배울 때 아래의 세 단계를 거칩니다.
첫번째 단계에선 기본적인 문법과 원칙을 학습합니다. 예를 들어 괄호와 꺾쇠 괄호는 언제 어떻게 사용하며 리스트와 벡터의 차이는 무엇인가 등입니다.
중간 단계에선 이제 조각을 맞춰가기 시작합니다. 불변 자료 구조를 적극적으로 사용하게 되고, 고차 함수를 실제 코드에 적용해보기도 합니다.
언어에 대한 이해를 마친 마지막 단계에선, 클로저 생태계를 탐험하기 시작합니다. 다른 사람들이 만든 방대한 라이브러리 와 애플리케이션을 새로운 지식과 접목해 프로그램을 만들어나갑니다.
진짜 재미의 시작이죠!
2.1 클로저 기초: 시작해보기
스크립트성 태스크
저는 2013년 텔레콤 부문에서 자바EE 프로젝트를 하며 클로저를 처음 접했습니다. 당시의 메인 웹 애플리케이션은 이제 막 Java7으로 마이그레이션 된 상태였습니다. try-with-resources와 NIO는 있었지만 arrow 함수, 자바 스트림 API나 jshell 같은건 없었습니다.
자바가 꽤 장황한 프로그래밍 언어로 인식된다는건 더 이상 비밀이 아닙니다. IDE, 최신 자바 문법과 API가 이 장황함을 어느 정도 성공적으로 가려줄 수는 있지만 말이죠.
자바16이 현재 최신 버전임에도 불구하고, 젯브레인(JetBrain)의 조사에 따르면 75%의 자바 개발자들이 2020년에도 Java8을 주로 사용하고 있고, 뉴렐릭(New Relic)은 80%가 넘는 상용 애플리케이션이 Java8을 사용하고 있다고 합니다.
자바는 스크립트성 태스크엔 적합하진 않습니다. 이것이 셀레늄 테스트에서 클로저를 고려하게 된 이유 중 하나입니다. 다른 이유는
- 안정적이며 JVM 친화적이다.
- 코드 재사용성과 중복 방지의 측면에서 자바와 호환성이 좋다.
- 재컴파일 필요 없이 REPL을 통해 인터랙티브 테스팅이 가능하다.
- (자바나 스칼라보다) 문법이 간단해서 QA 엔지니어들이 테스트 케이스를 스스로 수정할 수 있다.
가장 마지막 포인트에 대해선 의견이 분분했습니다. 함수형 언어로의 패러다임 전환은 숙련된 엔지니어들에게도 어려울 것이라는 걱정도 있었습니다. 하지만 팀은 당시 열정으로 가득 차 있었고, 혁신을 수용하는 분위기 속에서 다소 위험을 감수하고 클로저를 시도해보기로 정했습니다.
가장 먼저, 웹사이트와 컨트롤 misc UI 컴포넌트를 탐색하기 위한 헬퍼함수들(helper functions)을 clj-webdriver를 사용해 개발했습니다. 그리고 실제 테스트 케이스들을 작성했습니다.
REPL의 장점은 이제 누구나 아는 것이지만, 당시엔 정말 큰 반향을 일으켰습니다. 테스트 케이스들이 콘솔에서 바로(on-the-fly), 컴파일이나 스크립트를 재시작 할 필요 없이 작성되었고, 이 모든 것들이 JVM 위에서 돌아갔습니다. 믿을 수 없었죠!
여기 실제 테스트 케이스의 예시입니다.
1(ns project.tests2 (:use project.utils))34(defcase standart-create-qos "Create QoS policy"5 (select-main-menu "SLA management" "QoS policy")6 (press-button "Create new QoS policy")7 (input "Name" "test_qos_2")8 (select-option "Service class" "Class G")9 (select-option "Profile type" "test_profile")10 (set-quality-rule "For 15 minutes" "packet loss rate" "in the forward direction" "is less than" "10")11 (wait-response (press-button "Save"))12 (check-row-exists "test_qos_2"))
클로저로 작성된 마지막 테스트 케이스들은 읽기 쉽고, 친숙해 보였으며.. 명령형이었습니다! DSL이 QA 엔지니어들이 작성한 케이스의 3/4를 커버했습니다. 다른 케이스들은 개발자들이 투입되어야 했는데, 주로 QA를 도와 헬퍼 함수들을 짜는 역할을 보조했습니다.
클로저를 막 시작한 단계에서는 언어의 최소화된 문법 체계의 덕을 많이 봤습니다. (https://learnxinyminutes.com/docs/clojure/) 괄호에 대한 초보자들의 두려움은 정말 과장된 거라는 생각이 들었어요.
메이저 에디터와 IDE들은 paredit (혹은 parinfer) 플러그인을 지원합니다. 괄호의 짝을 맞춰주거나 실수로 삭제하는 걸 막아주고, 구조 수정을 쉽게 해주는 단축키를 제공해주는 등, 정말 단비와 같은 플러그인입니다. 아 래는 구조 수정(structural editing)의 예시입니다.
아래는 히컵 스타일(Hiccup-style)의 마크업입니다.
자바 인터롭은 굉장히 직관적입니다. 몇 가지 문법과 함수 호출이 리스트의 첫번째에 있다는 것만 기억하면 간단합니다.
예를 들어 아래의 자바 코드는,
1public ByteArrayInputStream toInputStream (String s, String charset)2 throws UnsupportedEncodingException {3 return new ByteArrayInputStream(s.getBytes(charset));4}
클로저에서는 아래와 같이 적힙니다.
1(defn string->stream [s charset]2 (-> s3 (.getBytes charset)4 (ByteArrayInputStream.)))
또한 클로저는 대부분의 자바 라이브러리와 모듈에 대한 래퍼(wrapper)를 제공합니다. 따라서 같은 코드를 자바를 호출하지 않고 바이트 스트림