Programming

Clojure 프로그래밍 언어(IBM developerWorks)

steloflute 2012. 11. 2. 21:13

http://www.ibm.com/developerworks/kr/library/os-eclipse-clojure/


Clojure 프로그래밍 언어

Clojure plug-in for Eclipse 활용하기

Michael Galpin, Software architect, eBay

요약:  Lisp은 표현 능력과 기능이 탁월한 프로그래밍 언어이지만 일반적으로 사용하기에는 부적절한 언어로 여겨졌습니다. 이러한 점은 Java™ 플랫폼에서 실행하는 Lisp 표현 형식인 Clojure로 인해 모두 변화되었습니다. 이제 편리한 JVM(Java Virtual Machine)이 있는 곳이면 어디에서나 Lisp의 강력한 기능을 이용할 수 있습니다. 이 기사에서는 Clojure를 시작하는 방법을 알아보고 Lisp을 학습하는 데 도움을 줄 수 있는 Clojure plug-in for Eclipse를 이용하여 일부 Lisp의 구문을 학습합니다.

원문 게재일:  2009 년 9 월 22 일 번역 게재일:   2009 년 10 월 20 일
난이도:  초급 영어로:  보기
페이지뷰:  5584 회

이 기사에서는 Clojure 프로그래밍 언어를 설명한다. Clojure는 Lisp 표현 양식이다. 사용자가 Lisp을 모르고 있다고 가정한다. 그대신 Java 기술에 대한 지식이 있다고 가정한다. Clojure 프로그램을 쓰려면 Java Development Kit V5 이상 및 Clojure 라이브러리가 있어야 한다. 이 기사에서는 JDK V1.6.0_13 및 Clojure V1을 사용하였다. 또한 Clojure plug-in for Eclipse(clojure-dev)를 이용해야 하고 이를 위해 Eclipse가 필요하다. 이 기사에서는 Eclipse V3.5와 clojure-dev 0.0.34를 사용했다. 관련 링크는 참고자료를 확인한다.

Clojure란?

얼마 전에는 JVM에서 프로그램을 실행하는 것이 곧 Java 프로그래밍 언어를 사용하여 프로그램을 작성하는 것을 의미했다. 이제는 다양한 선택을 할 수 있게 되어 그러한 의미는 오래전에 사라졌다. Groovy, Ruby (JRuby를 통해) 및 Python(Jython을 통해)과 같은 매우 인기있는 언어를 이용하면 보다 절차적인 프로그래밍 스크립팅 양식을 활용할 수 있으며 또한 언어마다 고유의 오브젝트 지향 프로그래밍 기능이 있다. Java 프로그래머에게 이러한 두 패러다임은 익숙한 내용일 것이다. 이 언어를 이용하여 Java 언어로 작성하는 것과 유사한 프로그램을 단지 다른 구문을 사용하여 작성한다고 반박할 수 있다.

Clojure는 JVM을 기반으로 한 또 다른 프로그래밍 언어이다. 그러나 Java 기술이나 이미 언급한 다른 JVM 언어와는 매우 다르다. Clojure는 Lisp의 표현 양식이다. Lisp 계열 프로그래밍 언어는 오래 전인 사실상 1950년대부터 있었다. Lisp은 별도의 S-expressions 또는 접두부 표기법을 사용한다. 이 표기법은 (함수 인수...)로 요약될 수 있다. 언제나 함수 이름으로 시작하며 0이나 다수의 인수를 나열하여 해당 함수에 전달한다. 함수와 인수는 괄호로 둘러싸서 함께 구성된다. 이것은 곧 Lisp을 상징(많은 괄호)하게 되었다.

짐작하는 바와 같이 Clojure는 함수형 프래그래밍 언어이다. 학계에서는 그 "단순함"에 대해 논란이 분분하지만 Clojure는 함수형 프로그래밍을 대표하는 뮤터블 상태(mutable state) 회피, 재귀, 고차 함수 등의 기능을 채택하고 있다. 또한 Clojure는 유형이 동적으로 변하는 언어이지만 유형 정보를 선택적으로 추가하여 코드에서 중요한 경로의 성능을 개선할 수 있다. Clojure는 JVM에서 실행될 뿐만아니라 Java와의 상호 운용성을 염두에 두고 디자인되었다. 마지막으로 Clojure는 동시성을 고려하여 디자인된 언어이며 동시 프로그래밍과 관련된 몇 가지 고유 기능을 갖고 있다.

Clojure 사용 예제

대부분 새로운 언어를 학습하기 위한 최상의 방법은 코드 작성을 시작하는 데 있다. 이러한 관점에서 몇 가지 간단한 프로그래밍 문제를 살펴보고 Clojure를 사용하여 이 문제를 해결해 본다. 이러한 해결책을 상세히 살펴봄으로써 Clojure가 동작하는 방식과 Clojure를 사용하는 방법 그리고 Clojure에 적합한 작업에 대해 더 잘 이해할 수 있다. 그러나 다른 언어와 마찬가지로 Clojure를 사용하는 데 필요한 개발 환경을 설정해야 한다. 다행히도 Clojure의 경우에는 이 작업이 매우 간단하다.

최소 설치

Clojure를 사용하려면 JDK와 하나의 JAR 파일인 Clojure 라이브러리가 있어야 한다. Clojure 프로그램을 개발하고 실행하는 방식은 두 가지가 있다. REPL(Read-eval-print-loop)을 사용하는 방식이 가장 일반적으로 사용된다.


Listing 1. Clojure REPL
$ java -cp clojure-1.0.0.jar clojure.lang.Repl
Clojure 1.0.0-
user=> 
            

명령은 Clojure JAR이 있는 디렉토리에서 실행된다. 필요에 따라 Clojure JAR 경로를 조정한다. 또한 스크립트를 작성하고 실행할 수 있다. 이렇게 하려면 clojure.main이라고 하는 Java 클래스를 실행해야 한다.


Listing 2. Clojure main
$ java -cp clojure-1.0.0.jar clojure.main /some/path/to/Euler1.clj 
233168
            

또한 Clojure JAR 경로와 해당 스크립트를 조정해야 한다. 마지막으로 Clojure에 대한 IDE 지원을 살펴보자. Eclipse 사용자는 Eclipse 업데이트 사이트를 이용하여 clojure-dev 플러그인을 설치할 수 있다. Eclipse가 설치되면 Java 퍼스펙티브가 생성되었는지 확인한 다음 아래와 같이 새로운 Clojure 프로젝트와 Clojure 파일을 만든다.


그림 1. Clojure plug-in for Eclipse(clojure-dev) 사용하기
Clojure plug-in for Eclipse(clojure-dev) 사용하기

clojure-dev를 이용하면 모든 Lisp의 필수 기능인 괄호 일치를 사용할 수 있으며 일부 기본적인 구문을 강조할 수도 있다. 또한 Eclipse에 직접 삽입된 REPL 스크립트를 실행할 수 있다. 이 기사를 쓰는 현재 플러그인은 여전히 매우 새롭고 그 기능은 빠르게 발전 중이다. 지금까지 기본적인 설치 과정을 살펴보았으므로 몇 가지 Clojure 프로그램을 작성하여 Clojure 언어를 검토해 보자.

예제 1: 시퀀스를 이용한 작업

Lisp이라는 이름은 "목록 처리(list processing)"라는 말에서 유래하며 Lisp의 모든 것은 목록(list)이라고 부르곤 한다. Clojure에서는 이것이 시퀀스로 일반화된다. 첫 번째 예제에서 다음과 같은 프로그래밍 문제를 살펴본다.

3이나 5의 배수인 10 이하의 모든 자연수를 나열하면 3, 5, 6, 9가 되며 이 배수의 합은 23이다. 3이나 5의 배수 중 1,000 이하인 모든 수의 합을 구해보자.

이 문제는 우수한 컴퓨터 프로그래밍 능력을 사용하여(때로는 그렇지 않을 수도 있음) 풀 수 있는 수학 문제를 모아놓은 Project Euler에서 가져왔다. 사실상 이 문제는 Problem No. 1이다. Listing 3에는 Clojure를 사용한 해답이 표시되어 있다.


Listing 3. Project Euler 예제 1
(defn divisible-by-3-or-5? [num] (or (== (mod num 3) 0)(== (mod num 5) 0))) 

(println (reduce + (filter divisible-by-3-or-5? (range 1000))))
            

첫 번째 라인에서는 함수를 정의한다. 기억할 점은 Clojure에서는 함수가 프로그램의 기본 빌딩 블록이라는 점이다. 대부분의 Java 프로그래머는 프로그램의 빌딩 블록이 되는 오브젝트에 익숙하며 따라서 함수에 익숙해지는 데 다소 시간이 걸릴 수 있다. defn을 Clojure의 키워드라고 생각할 수도 있지만 이것은 사실 매크로이다. 매크로를 사용하면 Clojure 컴파일러를 확장하여 해당 언어에 새로운 키워드를 사실상 추가할 수 있다. 따라서 defn은 언어 스펙의 일부가 아니라 언어의 코어 라이브러리로 추가된다.

이 경우에는 divisible-by-3-or-5?라고 하는 함수를 작성 중이다. 이는 Clojure 이름 지정 규칙을 따른다. 단어는 하이픈으로 분리하고 함수의 이름은 끝에 물음표를 붙이며 참이나 거짓을 리턴하는 조건부를 표시한다. 함수에는 num이라는 하나의 매개변수가 있다. 입력 매개변수가 더 있는 경우에는 매개변수가 공백으로 분리되어 대괄호 안에 표시된다.

다음은 함수의 본문이다. 첫 번째 사항은 or 함수라고 부른다. 이 함수는 일반적으로 사용되는 논리적 or이며 단지 하나의 함수이지 연산자는 아니다. 이것을 매개변수로 전달한다. 또한 이것은 표현식이기도 하다. 첫 번째 표현식은 == 함수를 사용하여 시작한다. 이 함수는 전달받은 매개변수를 값을 기반으로 비교한다. 이 함수에는 두 가지 매개변수가 전달된다. 첫 번째는 또 다른 표현식이며 이 표현식은 mod 함수라고 한다. 이 함수는 수학의 모듈 연산자이거나 Java 언어의 % 연산자이다. 따라서 이 경우에는 num을 3으로 나눈 나머지가 함수에서 리턴된 후 0과 비교된다. (나머지가 0이면 num은 3으로 나눠질 수 있다.) 마찬가지로 num을 5로 나눈 경우 나머지가 0인지 확인한다. 어느 한쪽의 나머지가 0이면 함수는 참을 리턴한다.

다음 라인에서는 표현식을 작성하고 출력한다. 먼저 가장 안쪽에 있는 괄호 안의 내용을 살펴보자. 여기서는 range 함수를 호출하여 숫자 1,000을 전달한다. 이렇게 하면 0에서 1,000 미만의 모든 숫자로 된 시퀀스가 작성된다. 이 시퀀스는 3이나 5로 나눠질 수 있는지 확인하고자 하는 숫자 세트와 정확히 일치한다. 이제 filter 함수를 살펴보자. 이 함수에는 두 개의 매개변수가 있는데, 하나는 참이나 거짓을 리턴해야 하는 또 다른 함수이고 다른 하나는 시퀀스이다. 이 경우에 시퀀스 요소는 0, 1, 2, ... 999를 말한다. filter 함수는 조건부를 적용하여 참을 리턴하는 경우 시퀀스의 해당 요소를 결과에 추가한다. 위 라인에 정의된 divisible-by-3-or-5?함수가 조건부이다.

그래서 필터 표현식은 각각 1,000 미만이고 3이나 5로 나눠질 수 있는 정수의 시퀀스가 된다. 이 시퀀스는 원하는 정수 세트와 정확히 일치하며 따라서 이것을 추가하기만 하면 된다. 이렇게 하려면 reduce 함수를 사용해야 한다. 이 함수에는 함수와 시퀀스라는 두 개의 매개변수가 있다. reduce 함수는 시퀀스의 첫 번째 두 요소에 해당 함수를 적용한다. 그런 다음 해당 함수를 이전 결과와 시퀀스의 다음 요소에 적용한다. 이 경우에 함수는 + 함수 또는 더하기를 말한다. 따라서 이 함수는 시퀀스의 모든 요소를 추가한다.

Listing 3을 살펴보면 적은 코드로 많은 작업을 수행하고 있음을 알 수 있다. 이점이 Clojure의 장점 중 하나이다. 많은 작업을 수행하지만 표기법에 익숙해지면 코드는 설명이 필요 없을 정도로 단순하다. 분명히 동일한 작업을 수행하려면 더 많은 Java 코드가 필요하다. 다음 예제를 살펴보도록 하자.

예제 2: 지연(lazy) 시퀀스 활용하기

이 예제에서는 재귀와 Clojure의 지연(lazy)을 살펴보자. 이는 대다수의 Java 프로그래머에게는 새로운 개념이다. Clojure에서는 요소가 필요할 때까지 해당 요소를 계산하지 않기 때문에 "지연" 시퀀스를 정의할 수 있다. 이렇게 하면 Java 언어에서 보지 못한 무한대의 시퀀스를 정의할 수 있다. 이러한 경우에 특히 유용한 예를 확인하기 위해 함수형 프로그래밍의 또 다른 중요한 면인 재귀와 관련된 예제를 살펴보자. 다시 한번 Project Euler의 프로그래밍 문제를 사용하지만 이 번 문제는 Problem No. 2이다.

피보나치 시퀀스에서 각각의 새 항목은 이전 두 항목을 더해서 생성된다. 1과 2를 시작으로 첫 번째 10개 항목은 1, 2, 3, 5, 8, 13, 21, 34, 55, 89가 된다.

시퀀스에서 4백만을 초과하지 않는 모든 짝수 값의 합을 구하라. 이 문제를 풀기 위해 Java 프로그래머는 n 번째 피보나치 숫자를 리턴하는 함수를 정의할 지도 모른다. 이러한 함수의 단순한 구현 예는 아래와 같다.


Listing 4. 단순한 피보나치 함수
(defn fib [n] 
    (if (= n 0) 0
        (if (= n 1) 1
            (+ (fib (- n 1)) (fib (- n 2))))))        
            

이 함수는 n이 0인지 확인한 후 맞으면 0을 리턴한다. 그런 다음 n이 1인지 확인한 후 맞으면 1을 리턴한다. 또한 (n-1) 번째와 (n-2) 번째 피보나치 숫자를 계산한 후 그 값을 서로 합한다. 이 함수는 분명히 올바르지만 Java 프로그래밍을 많이 해보지 않은 경우에는 문제에 부딪칠 수 있다. 이와 같은 재귀적 정의는 스택을 빠르게 채우게 되어 스택 오버플로우를 일으킬 수 있다. 피보나치 숫자는 무한 시퀀스를 형성하기 때문에 Clojure의 무한 지연(lazy) 시퀀스를 사용하여 그대로 기술해야 한다. 이러한 내용이 Listing 5에 기술되어 있다. Clojure에는 표준 라이브러리(clojure-contrib)에 포함된 보다 효과적인 피보나치 구현 방식이 있지만 그것은 너무 복잡해서 Stuart Halloway의 책(자세한 정보는 참고자료 확인)에서 아래의 피보나치 시퀀스를 가져왔다.


Listing 5. 피보나치 숫자를 구현하기 위한 지연(lazy) 시퀀스
(defn lazy-seq-fibo 
    ([] 
        (concat [0 1] (lazy-seq-fibo 0 1))) 
    ([a b] 
        (let [n (+ a b)] 
            (lazy-seq 
                (cons n (lazy-seq-fibo b n))))))
            

Listing 5에서는 lazy-seq-fibo 함수가 두 번 정의되어 있다. 첫 번째는 인수가 없이 정의되어 있고 따라서 대괄호는 비어 있다. 두 번째는 [a b]와 같이 두 개의 인수를 사용하여 정의되어 있다. 인수가 없는 경우에는 시퀀스 [0 1]을 취하여 표현식에 연결한다. 이 표현식은 lazy-seq-fibo를 재귀적으로 호출하지만 두 개의 인수가 있는 함수를 호출하는 경우에는 함수에 0과 1을 전달한다.

두 개의 인수가 있는 경우에는 let 표현식을 사용하여 시작한다. 이 표현식은 Clojure에서 변수를 할당한다. [n (+ a b)] 표현식은 변수 n을 정의하여 a+b와 같도록 설정한다. 그런 다음 lazy-seq 매크로를 사용한다. 이름에서 알수 있듯이 lazy-seq 매크로를 사용하여 지연(lazy) 시퀀스를 작성한다. 이 시퀀스의 본문은 표현식이다. 이 경우에는 cons 함수를 사용하고 있다. 이 함수는 고전적인 Lisp 함수이다. 이 함수는 요소와 시퀀스를 가져와서 해당 요소를 시퀀스 앞에 삽입한 후 새로운 시퀀스를 리턴한다. 이 경우에 시퀀스는 lazy-seq-fibo 함수를 다시 호출한 결과이다. 이 시퀀스가 지연되지 않으면 lazy-seq-fibo 함수는 반복해서 호출된다. 그러나 lazy-seq 매크로는 요소가 액세스될 때만 함수가 호출되도록 한다. 이 시퀀스가 동작하는지 확인하려면 Listing 6과 같이 REPL을 사용해야 한다.


Listing 6. 피보나치 숫자 생성하기
1:1 user=> (defn lazy-seq-fibo 
    ([] 
        (concat [0 1] (lazy-seq-fibo 0 1))) 
    ([a b] 
        (let [n (+ a b)] 
            (lazy-seq 
                (cons n (lazy-seq-fibo b n))))))
#'user/lazy-seq-fibo
1:8 user=> (take 10 (lazy-seq-fibo))
(0 1 1 2 3 5 8 13 21 34)
            

take 함수를 사용하여 시퀀스의 요소 중 특정 숫자(이 경우에는 10)를 가져온다. 이제까지 피보나치 숫자를 생성하는 방법을 살펴보았으니 문제를 풀어 보자.


Listing 7. 예제 2
(defn less-than-four-million? [n] (< n 4000000))

(println (reduce + 
    (filter even? 
        (take-while less-than-four-million? (lazy-seq-fibo)))))
            

Listing 7에서 less-than-four-million?를 호출하는 함수를 정의한다. 이 함수는 그 입력 값이 4백만 미만인지 여부를 간단히 테스트한다. 다음 표현식에서는 가장 안쪽에 있는 표현식을 살펴보도록 하자.먼저 무한 피보나치 시퀀스를 살펴보자. 그런 다음 take-while 함수를 사용한다. 이 함수는 take 함수와 유사하지만 조건부를 가져온다는 점이 다르다. 조건부에서 거짓을 리턴하면 이 함수는 시퀀스에서 요소를 가져오지 않는다. 따라서 이 경우에는 피보나치 숫자가 4백만을 넘는 즉시 가져오기를 멈춘다. 그런 다음 결과를 가져와서 filter에 적용한다. filter는 내장된 even? 함수를 사용한다. 이 함수는 단지 숫자가 짝수인지 여부를 테스트한다. 그 결과는 4백만 미만의 모든 짝수 피보나치 숫자가 된다. 이제 첫 번째 예제에서 했던 것과 마찬가지로 reduce 함수를 사용하여 그 결과를 모두 합한다.

Listing 7를 사용하면 문제가 바로 풀리지만 그다지 만족스럽지는 않다. take-while 함수를 사용하려면 less-than-four-million?이라고 하는 매우 간단한 함수를 정의해야 한다. 이 함수는 필요 없다는 사실이 아래에서 증명된다. Clojure에서 클로저(closure)를 지원한다는 사실은 놀랄만한 일도 아니다. 클로저를 사용하면 Listing 8에서와 같이 코드를 단순화할 수 있다.

Clojure에서 클로저 사용하기

클로저는 많은 프로그래밍 언어에서 일반적으로 사용되며 특히 Clojure와 같은 함수형 언어에서 그러하다. 함수는 인수로서 다른 함수에 전달될 수 있는 첫 번째 요소일 뿐만아니라 인라인이나 익명으로 정의될 수 있다. Listing 8에는 클로저를 사용하여 Listing 7을 단순화한 코드가 표시되어 있다.


Listing 8. 보다 간단한 해결책
(println (reduce + 
    (filter even? 
        (take-while (fn [n] (< n 4000000)) (lazy-seq-fibo)))))
            

Listing 8에서는 fn 매크로를 사용했다. 이 매크로는 익명 함수를 작성하여 리턴한다. 조건부 함수는 일반적으로 매우 단순하며 클로저를 사용하여 정의하는 편이 더 좋다. 증명된 바와 같이 Clojure는 훨씬 더 축약된 방식으로 클로저를 정의한다.


Listing 9. 단축 클로저
(println (reduce + 
    (filter even? 
        (take-while #(< % 4000000) (lazy-seq-fibo)))))
            

fn 매크로 대신 #을 사용하여 클로저를 작성했다. 또한 함수에 전달되는 첫 번째 매개변수에 % 기호를 사용했다. 또한 함수에서 다수의 매개변수를 처리할 수 있는 경우 첫 번째 매개변수에 %1, %2, %3 등을 사용할 수 있다.

이러한 두 가지 간단한 예제를 사용하여 Clojure의 다양한 기능을 알아보았다. Java 언어와의 완벽한 통합은 Clojure의 또 다른 중요한 기능이다. Clojure에서 Java를 활용하는 또 다른 유용한 예제를 살펴보자.

예제 3: Java 기술 사용하기

Java 플랫폼은 다양한 기능을 제공한다. Java 언어로 쓴 다양한 써드파티 라이브러리와 코어 API의 풍부한 기능과 JVM의 성능은 모두 강력한 도구이며 이러한 도구를 이용하면 많은 함수를 다시 작성해야 하는 수고를 덜 수 있다. Clojure는 이러한 생각을 바탕으로 빌드된다. Java 메소드를 호출하고, Java 오브젝트를 작성하고, Java 인터페이스를 구현하고, Java 클래스를 확장하기가 쉽다. 이와 관련된 몇 가지 예를 확인하기 위해 또 다른 Project Euler 문제를 살펴보자.


Listing 10. Project Euler Problem No. 8
자리 수가 1,000인 숫자에서 연속된 5개의 자리 수로 된 숫자 중 가장 큰 수를 구하라.
73167176531330624919225119674426574742355349194934 
96983520312774506326239578318016984801869478851843 
85861560789112949495459501737958331952853208805511 
12540698747158523863050715693290963295227443043557 
66896648950445244523161731856403098711121722383113 
62229893423380308135336276614282806444486645238749 
30358907296290491560440772390713810515859307960866 
70172427121883998797908792274921901699720888093776 
65727333001053367881220235421809751254540594752243 
52584907711670556013604839586446706324415722155397 
53697817977846174064955149290862569321978468622482 
83972241375657056057490261407972968652414535100474 
82166370484403199890008895243450658541227588666881 
16427171479924442928230863465674813919123162824586 
17866458359124566529476545682848912883142607690042 
24219022671055626321111109370544217506941658960408 
07198403850962455444362981230987879927244284909188 
84580156166097919133875499200524063689912560717606 
05886116467109405077541002256983155200055935729725 
71636269561882670428252483600823257530420752963450
            

이 문제에서는 자리 수가 1,000인 숫자가 주어진다. 이러한 숫자는 BigInteger를 이용한 Java 기술을 사용하여 나타낼 수 있다. 그러나 전체 숫자를 계산할 필요는 없고 단지 한번에 다섯 자리만 계산하면 된다. 따라서 이 숫자를 문자열로 처리하는 것이 보다 편하다. 그러나 계산을 하려면 자리 수를 정수로 처리해야 한다. 다행히도 문자열과 정수를 서로 변환할 수 있는 Java 언어 API가 있다. 먼저 위에 있는 대규모의 불규칙한 텍스트를 처리해야 한다.


Listing 11. 텍스트 구문 분석
(def big-num-str 
    (str "73167176531330624919225119674426574742355349194934
96983520312774506326239578318016984801869478851843
85861560789112949495459501737958331952853208805511
12540698747158523863050715693290963295227443043557
66896648950445244523161731856403098711121722383113
62229893423380308135336276614282806444486645238749
30358907296290491560440772390713810515859307960866
70172427121883998797908792274921901699720888093776
65727333001053367881220235421809751254540594752243
52584907711670556013604839586446706324415722155397
53697817977846174064955149290862569321978468622482
83972241375657056057490261407972968652414535100474
82166370484403199890008895243450658541227588666881
16427171479924442928230863465674813919123162824586
17866458359124566529476545682848912883142607690042
24219022671055626321111109370544217506941658960408
07198403850962455444362981230987879927244284909188
84580156166097919133875499200524063689912560717606
05886116467109405077541002256983155200055935729725
71636269561882670428252483600823257530420752963450"))
            

여기서는 Clojure에서 지원하는 다중 행 문자열을 이용한다. str 함수를 사용하여 다중 행 문자열 리터럴을 구문 분석한다. 그런 다음 def 매크로를 사용하여 big-num-str이라고 하는 상수를 정의한다. 그러나 리터럴을 정수 시퀀스로 변환하는 것이 가장 유용하다. 이 과정은 Listing 12에 표시되어 있다.


Listing 12. 숫자 시퀀스 작성하기
(def the-digits
    (map #(Integer. (str %)) 
        (filter #(Character/isDigit %) (seq big-num-str))))
            

다시 한번 가장 안쪽에 있는 표현식을 살펴보자. seq 함수를 사용하여 big-num-str를 문자열로 변환한다. 그러나 이 시퀀스가 원하는 것과 다르다는 것을 알 수 있다. 아래와 같이 REPL을 사용하여 이점을 확인할 수 있다.


Listing 13. big-num-str 시퀀스 시험하기
user=> (seq big-num-str)
(\7 \3 \1 \6 \7 \1 \7 \6 \5 \3 \1 \3 \3 \0 \6 \2 \4 \9 \1 \9 \2 \2 \5 \1 \1 \9
 \6 \7 \4 \4 \2 \6 \5 \7 \4 \7 \4 \2 \3 \5 \5 \3 \4 \9 \1 \9 \4 \9 \3 \4
 \newline...
            

REPL은 \c로 문자(Java 문자)를 표시한다. 따라서 \7은 문자 7이 되고 \newline은 문자 \n(개행 문자)이 된다. 이렇게 하면 텍스트를 직접 구문 분석할 수 있다. 계산을 하기 전에 명확하게 개행 문자를 제거하고 정수로 변환해야 한다.이 작업이 Listing 11에서 해야 할 일이다. Listing 11에서 filter를 사용하여 개행 문자를 제거한다. filter 함수에 전달된 조건부 함수에 다시 한번 단축 클로저를 사용했음에 유의한다. 클로저는 Character/isDigit을 사용하고 있다. 이것은 java.lang.Character의 정적 메소드인 isDigit이다. 따라서 이 filter를 사용하면 숫자는 남고 개행 문자는 없어진다.

이제 개행 문자를 제거했으므로 정수로 변환해야 한다. Listing 12의 안쪽에서 함수와 시퀀스를 매개변수로 하는 map 함수를 사용하였음에 유의한다. 이 함수는 새로운 시퀀스를 리턴하며 이 시퀀스의 n 번째 요소는 원래 시퀀스의 n 번째 요소에 함수를 적용한 결과이다. 이 함수에서 다시 한번 단축 클로저 표기를 사용한다. 먼저 Clojure에서 str 함수를 사용하여 문자를 문자열로 변환한다. 이렇게 하는 이유는 다음에 java.lang.Integer 생성자를 사용하여 정수를 작성할 것이기 때문이다. 이 작업은 Integer로 표시된다. 이 표현식을 새로운 java.lang.Integer(str(%))로 생각할 수 있다. map 함수와 함께 이 표현식을 사용하면 원하는 정수 시퀀스를 얻게 된다. 이제 문제를 풀 수 있다.


Listing 14. 예제 3
(println (apply max 
    (map #(reduce * %)
        (for [idx (range (count the-digits))] 
            (take 5 (drop idx the-digits))))))
            

이 코드를 이해하기 위해 먼저 for 매크로를 살펴보자. 이 매크로는 Java 언어의 for 루프와는 다르다. 대신에 이 매크로는 일종의 시퀀스 컴프리헨션이다. 먼저 대괄호를 사용하여 바인딩을 작성한다. 이 경우에는 idx 변수를 0에서 N - 1까지의 시퀀스에 바인딩하였으며 여기서 N은 시퀀스 the-digits의 요소 수이다. (원래 숫자의 자리 수가 1,000이므로 N = 1,000이다.) 다음은 for 매크로가 표현식을 사용하여 새로운 시퀀스를 생성한다. 이 매크로는 idx 시퀀스의 각 요소를 대상으로 반복해서 표현식을 평가하고 그 결과를 리턴 시퀀스에 추가한다. 어떤 면에서는 이 매크로가 for 루프와 유사한 형태의 동작을 한다는 것을 알 수 있다. 컴프리헨션에 사용된 표현식은 먼저 drop 함수를 사용하여 시퀀스의 첫 번째 M 요소를 삭제한 후 take 함수를 사용하여 축약된 시퀀스의 첫 번째 다섯 요소를 가져온다. M은 차례로 0, 1, 2 등이 되고 따라서 그 결과는 중첩 시퀀스가 되며 여기서 첫 번째 요소는 (e1, e2, e3, e4, e5)가 되고 다음 요소는 (e1, e2, e3, e4, e5, e6) 등이 되며 여기서 e1,e2 등은 the-digits의 요소이다.

중첩 시퀀스를 사용했으므로 이제 map 함수를 사용한다. reduce 함수를 사용하여 5개의 숫자로 구성된 각 시퀀스를 5개의 숫자로 된 결과로 변환한다. 이제 정수 시퀀스가 생겼으며 여기서 첫 번째 요소는 요소 1 - 5의 결과이며 두 번째 요소는 요소 2 - 6의 결과이다. 다음은 최대값을 계산해야 한다. 이렇게 하려면 max 함수를 사용해야 한다.그러나 max 함수에는 하나의 시퀀스가 아니라 다수의 요소를 전달해야 한다. 이 시퀀스를 다수의 요소로 변환하여 max 함수에 전달하려면 apply 함수를 사용해야 한다. 이렇게 하면 이 문제를 해결하는 데 필요한 최대값이 계산되고 그 해답이 출력된다. 이제까지 Clojure를 사용하는 방법을 학습하면서 몇 가지 문제를 풀었다.

요약

이 기사에서 Clojure 프로그래밍 언어를 소개했으며 Clojure plug-in for Eclipse를 유용하게 사용하였다. Clojure의 디자인 개념과 기능을 살펴보았으며 코드 예제를 집중적으로 검토하였다.이러한 간단한 예제를 통해 함수, 매크로, 바인딩, 재귀, 지연(lazy) 시퀀스, 클로저, 컴프리헨션(comprehension) 및 Java 기술과의 통합과 같은 Clojure의 다양한 핵심 기능을 살펴 보았다. Clojure에는 보다 다양한 기능이 있다. 이 언어에 관심을 갖고 일부 참고자료를 살펴보면서 더 자세히 학습하기를 바란다.


다운로드 하십시오

설명이름크기다운로드 방식
Article source codeos-eclipse-clojure-euler.zip2KBHTTP

다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기

  • Java Development Kit V5 이상이 필요하다. 이 기사에서는 Java Development Kit V1.6.0_13을 사용한다.

  • Clojure V1을 다운로드한다.

  • 최신 Eclipse IDE를 다운로드하자. 이 기사에서는 Eclipse V3.5를 사용하였다.

  • clojure-dev는 Clojure 프로그래밍 언어를 위한 IDE이며 Eclipse 플랫폼에 내장되어 있다. 이 기사에서는 V0.0.34를 사용하였다.

  • DVD로 제공되거나 다운로드할 수 있는 IBM 시험판 소프트웨어를 사용하여 후속 오픈 소스 개발 프로젝트를 구현해 보자.

  • IBM 시험판 제품을 다운로드하거나 IBM SOA Sandbox의 온라인 시험판을 살펴보고 DB2®, Lotus®, Rational®, Tivoli® 및 WebSphere®의 애플리케이션 개발 도구 및 미들웨어 제품을 사용해 볼 수 있다.

토론

필자소개

Michael Galpin's photo

Michael Galpin has been developing Java software professionally since 1998. He currently works for eBay. He holds a degree in mathematics from the California Institute of Technology.