01. 클로저

그루비에서 가장 개념적으로 이해하기 어려울 수 있고, 생소할 수도 있는 영역이 바로 클로저(Closure) 이다. (함수형 언어 Clojure가 아닌, 그루비에서의 Closure 라는 개념에 대해서 설명한다)

클로저란 무엇인가?



클로저는 굉장히 중요하다. 자주 쓰이기도 하거니와 그만큼 강력한 기능을 제공하기 때문이다.

그러나, 앞선 예제들에서 우리는 이미 클로저를 접해보았다.

아닌거 같지만 아래의 구문을 보면 "아하" 하게 될 것이다.

list.each{ firstName->
            println "list = " + firstName
}


이전 강좌에서 봤더 예제이다.
요소 객체를 순환하며 뭔가를 동작하는 이 구문의 시작 부터가 클로저를 얘기한다.

클로저는 객체의 동작을 주목적으로 하는 일종의 코드 블럭이다. 또한 클로저는 하나의 객체이다.
인자를 받고, 값을 리턴할 수 있다는 점에서 메소드 처럼 동작한다. 그러나 메서드 처럼 반드시 메서드 명과 같은 시그니쳐를 선언하지 않아도 동작 할 수 있다, 즉, 변수처럼 사용할 수 있는 익명의 코드 블럭과 같은 것이라고 생각하면 된다.

위의 코드 구문에서 메서드의 호출에 클로저 인자를 전달하는데, 전달된 값의 이름이 없어도 디폴트로 "it" 을 만들어 준다. 따라서 코드가 간결해진다.

아래의 소스를 보자

package org.gliderwiki.project.closure

class CallParamClosure {

	static void main(args) {
		def multiple = {
			return it * 2
		}
		
		[2,4,8].each {  
			println "${it} * 2 = ${multiple(it)}"
		}
    }	
}


multiple 이라고 하는 변수에 단순히 요소의 *2 를 하여 되돌려 준다. 그러면 여러값을 값는 요소의 each구문이 실행될때 multiple 변수가 호출되며 연산 후 되돌려주는 비교적 간단한 소스이다.
실행 결과는 아래와 같다.

실행결과
2 * 2 = 4
4 * 2 = 8
8 * 2 = 16


자바의 컬렉션에 정형화된 문법인 반복문과 (자바에서는 반복문의 본체에 코드가 삽입되어야 한다) 리소스를 쓰는 (예를 들면 stream 계열)을 써야 할때 클로저를 활용하면 소스코드가 굉장히 심플해지고, 리소스에 대한 콜백(리소스를 선언하고 쓰고 되돌려주는일) 을 클로저를 통해 쉽게 코딩하고 해결할 수 있다.
또한 클로저는 코드 블럭을 (함수) 정의 한 후, 실행되는 시점에 해당 코드가 수행이 된다.

반복문의 수행에 관한 추가된 몇가지 메서드 [each, find, findAll, collect, inject] 들을 사용하여 소스의 분리, 재정의, 제어의 단순함을 추구할 수있다.

아래의 코드를 살펴보자.
이 코드는 지정된 위치의 파일을 읽어 라인별로 출력하는 간단한 소스코드 입니다.

package org.gliderwiki.project.file

class FileExample {

	public static void main(def args) {
		File  file = new File("/Users/inamhui/Documents/공감세미나.txt")  // c:/temp/myfile.txt
		file.eachLine { line -> 
			println line
		}
	}
}


라인별로 전달된 클로저의 코드에서 리소스 관리는 알아서 다 해주게 됩니다.
이 코드를 좀 더 단순화 시키면 아래와 같습니다.

new File("C:공감세미나.txt").eachLine { println it } 


말 그대로 it's over!
eachLine이 파일을 열고 사용하고 스트림을 닫는 일까지 관리 합니다. 따라서, 리소스가 새는 문제를 막아줍니다.

이는 클로저의 빙산에 일각입니다. 네트워크 연결, DB 커넥션, GUI, 그래픽 등등 리소스를 관리해야 할 거의 모든 영역에서 "디자인 패턴" 이나 "견고한 프로그래밍 기법"을 통하지 않아도 간단하고 유연하게 작성할 수 있습니다.

자바에서 익명 내부 클래스(anonymous inner class)를 써야 하는 상황이라면, 이러한 그루비의 클로저가 훌륭한 대안이 되었을 것입니다.

클로저 정의하기



아래의 코드를 보겠습니다.
package org.gliderwiki.project.closure

class DefineClosure {
	
	static void main(args) {
		def log = ''
		(1..10).each { counter ->  log += counter } 
		assert log == '12345678910'
		
		log = '' 
		(1..10).each { log += it } 
		assert log == '12345678910'
	}
}


이 두 코드 블럭은 같은 로직을 수행합니다.
클로저의 인자를 받을때 디폴트로 it를 쓰는것과 쓰지 않는 것의 차이만 있을 뿐, 동일한 로직을 수행합니다.

인자를 나타내는 화살표는, 왼쪽의 메서드가 오른쪽의 클로저에게 전달한다고 이해하시면 됩니다.

기본적인 클로저의 정의는 이런 형태이지만 좀 더 간단하게 정의 할 수 도 있습니다.
바로, 할당으로 전달하는 방법 입니다.

def  printer  = { line -> println line   }


또는 메서드의 리턴값에 클로저를 할당하는 경우도 있습니다.

def  Closure getPrinter() {
        return { line  ->  println  line  }
} 


클로저의 중괄호를 보면 습관적으로 new Closure() { ... } 생각하면 이해하기 쉬울것입니다.

아래는 모두 클로저를 수행하는 코드입니다.

class EveryClosure {

	static void main(args) {
		def value = [1, 2, 3].every { it < 5 }
		println value   // true
		
		value = [1, 2, 3].every { item -> item < 3 }
		println value   // false
    }
}



class CallClosure { 
	static void main(args){
		def adder = { x, y -> return x+y }
		
		println (adder(2,3))
		println adder.call(2,6)
	}
}


class InjectClosure {

	static void main(args) {
		def value = [1,2,3].inject('counting : ' ) {
			str , item -> str + item
		}
		println value    //  counting : 123
		
		value = [1,2,3].inject(2) { 
			count, item -> count + item 
		}
		println value    // 8 
    }
}



클로저의 리턴 구문



클로저를 호철 한 후 내부적인 로직을 수행하다 리턴을 해야 할 수도 있습니다.

이럴때 원칙적으로 리턴하는 방법은 두가지 형태로 가능합니다.
  • 클로저가 실행하다 마지막 문장을 평가하고 나면, 그 결과 값이 리턴됩니다.
  • 이것을 end return이라고 하는데, return 키워드는 써도 되고 쓰지 않아도 됩니다.
  • 명시적으로 수행중 리턴을 해줘야 할 경우 return을 적어줘야 합니다.
아래의 두 코드 모드 동일한 동작을 합니다.

[1, 2, 3].collect {   it  *  2 } 
[1, 2, 3].collect {   return   it  *  2 } 



또한 아래 처럼 명시적으로 return을 해줘야 할 때도 있습니다.
[1, 2, 3].collect {   
        if ( it % 2 == 0 )  return it  *  2 
        return it 
}

이해하기 어렵지 않지만 하나는 주의 해야 합니다.

'return' 키워드는 클로저의 내부와 외부에서 다르게 동작합니다.


정리하며..



클로저의 가장 강력한 부분은 리소스관리와 패턴, 빌더등의 적용에서 발휘됩니다.
네트워크 연결, DB 커넥션, GUI, 그래픽, SWT등의 영역인데, 아래의 코드로 좀 더 직관적인 클로저 코드를 살펴보도록 하겠습니다.
import groovy.sql.Sql

class MySQLConnect {
	static void main(args) {
		def sql = Sql.newInstance('jdbc:mysql://14.63.225.xxx:3306/wikitestDB', 'testUser', 'password', 'com.mysql.jdbc.Driver')
		sql.eachRow('show tables'){ row ->
			println row[0]
		}
    }
}


출력결과는 해당 DB에 생성된 테이블 목록을 조회하게 됩니다.
심플함과 직관적인데다 리소스의 반환도 따로 관리하지 않아도 되므로 가급적 클로저를 익숙하게 다루는 것이 그루비를 그루비 스럽게 코딩하는 관건이 될것이라 생각됩니다.