Domain-Specific Language(DSL)은 특정 도메인에 특화된 비교적 작고 간단한 프로그래밍 언어이다. 도메인 문제를 해결 할 때, 문제 해결 아이디어와 최대한 유사한 형태의 문법으로 프로그래밍 할 수 있도록 하는 것이 주요 컨셉트이다.
DSL의 반대 개념은 General Purpose Language(GPL)이며 Java나 C++같은 보편적인 언어를 지칭한다. GPL로는 컴퓨터로 해결 가능한 모든 종류의 문제를 풀 수 있지만 그것이 항상 최선의 방법은 아니며 DSL 바로 그 특정한 종류(도메인 영역)의 문제를 해결하기에 더 적합한 언어가 되는 것을 그 목적으로 한다.
이클립스에서는 Xtext를 이용하여 DSL을 정의하고 에디터 및 DSL 기반 인터프리터 또는 제네레이터를 공급할 수 있다. GPL 중에서도 Java Script등과 같은 다이니믹 타이핑 언어등을 개발할 때에는 DLTK를 이용할 수 있다.
1. DSL이란 특정 도메인(산업, 분야등 특정 영역)에 특화된 언어를 말한다. "문제 영역의 해결에는 그 영역의 언어를 전제로 둬야하며, 거기에서 프로그래밍 솔루션을 꺼내는 것이 중요하다." 라고 Dave Thomas가 한 말을 생각하면 이해하기 쉽다.
특정 영역의 문제 해결에는 그 영역에 맞는 특화된 도구를 사용하자라는 것이다. 어찌보면 과도로 끝내도 될 일을 맥가이버칼을 들이대는 격이다. 그리고 표현 방식은 해당 도메인의 전문가가 이해할 수 있는 형태(고급 언어)여야 한다.
실제로 Ruby처럼 그 코드가 일반 자연어를 읽는 것과 같이 쉽게 이해되기 때문에 도메인 전문가와 프로그래머가 아이디어를 공유하기 좋고 거기에 Lisp 계열이어서 DSL에 많이 활용된다.
그리고 DSL은 개발 생산성과 도메인 전문가와 커뮤니케이션을 원할하게 하기 위해서 도입되는 경향이 많지만, No Silver Bullet.
참고로 UNIX에서도 특정한 응용 영역의 문제를 해결하기 위해 그 영역에만 적용할 수 있는 특수한 언어를 만들어 문제를 해결하는 오랜 전통이 존재한다. 이런 언어를 "작은 언어(little language)", 또는 "미니 언어(mini language)"라고 부르는데 DSL도 이와 유사하다고 볼 수 있다.
2. 내부 DSL과 외부 DSL 2.1 내부 DSL - 호스트 언어 구문을 이용하여 자체적으로 의존하는 무언가를 만드는 경우에 해당된다. - 내부 DSL에서는 API와 DSL의 경계가 모호해 비슷하게 생각하는 경향이 있다. . 좀 더 일반 사용자가 알아보기 쉬운 API가 내부 DSL로 생각해도 될 듯 하다. - 호스트 언어 능력과 지금까지 사용하던 도구를 그대로 사용할 수 있다는 점, 처리 결과를 쉽게 예측할 수 있어서 해당 언어를 잘 알면 친근할 수 있다. - 형태 . 메타 프로그래밍의 형태로 언어에 미니 언어를 만들 수 있다. . 원래 언어로 새로운 구문으로 도입 된다. 그래서 언어 확장을 일으켜 다른 언어가 된다. . 인라인 코드 형태로 표현될 수도 있다. - 적합한 언어 : Lisp, Ruby, Smalltalk
2.2 외부 DSL - 호스트 언어와 다른 언어 (XML, Makefile과 같은 고유 형식)에서 생성된 DSL. - GUI 도구를 제공해 주는 것이 특징. - 외부 DSL에서는 DSL과 범용 언어(GPL : General Purpose Language)과의 경계가 모호해지는 경향이 있다. . 그 차이는 언어 작성자와 언어 사용자의 목적에 있다. 특정 영역에서 언어의 작성자가 아닌 사용자의 목적에 부합하는, 이해를 할 수 있으면 외부 DSL이다. - 외부 DSL 개발자가 자유롭게 DSL의 형식을 결정할 수 있다. - 형태 . 실행 파일에서 DSL 을 동적 로딩할 수 있다. . DSL 컴파일러를 만들어서 표현할 수 있다. . DSL을 범용 언어로 코드로 변환한다. - 적합한 언어 : Java, C#, C++
3. DSL의 장점과 단점 3.1 장점 - 반복이 제거되고 비슷한 처리 코드는 자동 생성(템플릿) 된다. - 프로그래밍 코드의 양이 적고 가독성이 높다. - 특정 프로그래머(lay programer - martin fowler)들과 커뮤니케이션이 쉽다. . XML, CSS, SQL 등
3.2 단점 - 설계가 어렵다. - 잘 설계가 되지 않는다면 읽기 어려운 코드가 될 수 있다. - 하위 호환성을 유지해야 한다.
4. 우리 주변에 있는 DSL - java . ANT, Maven, struts-config.xml, Seasar2 S2DAO, HQL(Hibernate Query Language), JMock - Ruby . Rails Validations, Rails ActiveRecord, Rake, RSpec, Capistrano, Cucumber - 기타 . SQL, CSS, Regular Expression(정규식), Make, graphviz
자바의 접근제한자를 쓸수 있으며 리턴형은 생략가능하다. 접근제한자나 리턴형을 지정하지 않을 때는 키워드 def를 사용한다. def를 쓰면 리턴값의 자료형이 지정되지 않았다고 생각할수 있다 (물론, 리턴이 없는 void메서드일수도 있다) 내부적으로 java.lang.Object가 리턴된다. 접근제한자는 def로 선언시 public선언된다.
main 메서드에 흥므로운 것이 있다 첫째는 : public디폴트이기 때문에 생략 둘째는 : 실행할 수 있는 클래스의 메인메서드는 인자가 String[] 이어야 한다. 여기서 arg가 암묵적으로Object 가 되는데도 그루비의 메서드 디스패치 덕분에 메인 메서드로 동작한다. 여기서 void도 생략가능하다 메인 메서드로 동작한다.static main(args)
Caught: groovy.lang.MissingMethodException: No signature of method: static _6.SomeClass.method() is applicable for argument types: (java.lang.Integer, java.lang.Integer, java.lang.Integer) values: [1, 2, 3]
Possible solutions: method(java.lang.Object), method(java.lang.String), method(java.lang.Object, java.lang.Number), method(java.lang.Object, java.lang.Object, java.lang.Object)
1. 그루비 파일에 클래스 정의가 ‘하나도 없다면’ 그파일은 스크립트로 동작한다. 다시말해 자동으로 Script 클래스를 상속받는다. 자동으로 생성된 클래스의 이름은 확장자를 뺀 소스 파일의 이름과 같다. 파일의 코드는run메서드에 들어가고, 실행할 수 있겠끔 main 메서드도 만들어진다. 2. 그루비 파일에 클래스가 ‘하나’ 만 있고, 그 클래스 이름이 확장자를 제외한 소스 파일의 이름과 같다면 자바와 같은 일대일 대응 관계가 된다. 3. 그루비는 그루비 파일에 클래스가 ‘몇개’ 정의되어있든 그것들이 public이든 private이든 소스파일의 이름과 같은 클래스가 있던 없던 다수용할수 있다. 그루비 컴파일러인 groovyc는 파일에 정의된 클래스 각각에 대해 *.class 파일을 생성한다. 이파일을 프롬프트에서 goovy를 통해서 실행하거나 IDE에서 호출하는 식으로 스크립트로서 사용할 때는 파일에서 첫번째로 정의된 클래스에 main 메서드가 있어야한다. 4. 그루비 파일에 클래스 정의와 스크립트 코드를 섞어서 쓸수도 있다 이렇게 하면 실행시에 주 클래스는 스크립트 코드가 된다 따로 소스 파일이 이름과 같은 이름으로 클래스를 만들 필요는 없다.
명시적으로 컴파일 하지 않았다면 그루비에서 클래스를 찾을 때는 클래스 이름 과 같은 *.groovy소스 파일을 검색한다. 이때 이름이 중요하다 그루비는 찾고 있는 클래스와 이름이 같은 파일만 검색한다 , 파일을 발견하면 그파일 안에 모든 클래스를 분석해서 로딩한다.
*.groovy소스 파일들은 *.class파일로 컴파일하지 않아도 쓸수있기때문에 클래스를 찾을때 *.groovy 파일도 함께 찾는다. 이때도 같은 방식으로 검색한다 즉 그루비는 business/Vendor.groovy파일에서 business 패키지에 속한 Vendor 클래스를 검색할것이다.
그루비가 *.groovy 파일을 찾을때 클래스 패스를 사용한다. 주어진 클래스를 찾다가 *.class 와 *.groovy 모두 발견됐다면 둘중 최근에 변경된 파일을 사용한다 즉 *.groovy 가 *.class보다 최근에 변경됐다면 *.groovy 컴파일한후 *.class를 사용한다.
그루비가 *.class 파일과 *.groovy 파일에서 클래스를 찾아낸다는 점은 그루비를 다를때 이해하고 있어야 하는 중요한 부분이다. 안타깝게도 여기서 문제가 종종 발생하는데...
그루비의 클래스패스는 %GROOVY_HOME%\conf 디렉터리의 특별한 설정파일이있다. groovy-starter.conf
맨마지막줄 #지워서 활성화시키면 좋은 기능이 살아난다. user.home으로 상징되는 사용자의 홈 디렉터리에서 서브디렉터리로 .groovy/lib를 만들고 *.class나 *.jar 파일 넣어두면 그루비가 쓸때마다 로딩되도록한다. user.home찾기힘들다면 groovy –e “println System.properties.’user.home’”
1. 상속하기 그루비에서는 그루비와 자바의 클래스나 인터페이스를 상속받아서 확장할 수 있다. 자바 쪽에서도 그루비 클래스나 인터페이스를 상속받을수 있다. 2. 인터페이스 자바의 인터페이스를 완벽하게 지원한다. 자바의 추상 메서드도 지원한다. 그루비는 더 동적으로 인터페이스를 사용할 수 있는 기능을 제공한다. 메서드가 하나만 있는 인터페이스인 MyInterface와 클로저 myClosure가 있다면 이 myClosure를 키워드 as 이용하여 MyInterface로 강제 형변환할수도있다
필드 변수와 같은 영역에서는 fieldname 이나this.fieldname을 필드 변수에 대한 접근으로 해석하며 프로퍼티에 대한 접근으로 보지 안흔ㄴ다 영역 밖에서는 referenc.@fieldname문법을 이용해야 동일한 효과를 얻는다. 주의할것이 하나있는데 정적인 영역 (static context)에서 @를 사용하거나 def x = this;x.@fieldname 같은식으로 사용하면 이상한 현상이 발생한다. 권장하지 않는다.
4. object.get(name)메서드를 만들면 필드 변수 접근을 가로챌수 있다 이부분은 그루비 실행환경의 최전방에 속한다 자바빈에 적당한 프로퍼티도 없고 getProperty메서드도 구현되지 않았을때 사용된다. 그리고 당연히 name에 특수 문자가 있어서 식별자(identifier)로 사용할수 없을 때도 문자열로 만들면 사용할수 있다 즉 object.’my-name’과같이 만들면된다 또 GString 이용할수도있다. def name = ‘my-name’;Object.”$name” 처럼 써도된다 Object에는 getAt이라는 메서드가 있어서 object[name] 형식으로 프로퍼티에 접근하도록 위임할수 있다.
this.class.methods : [public void _22._22.super$1$wait(), public void _22._22.super$1$wait(long,int), public void _22._22.super$1$wait(long), public java.lang.String _22._22.super$1$toString(), public void _22._22.super$1$notify(), public void _22._22.super$1$notifyAll(), public java.lang.Class _22._22.super$1$getClass(), public boolean _22._22.super$1$equals(java.lang.Object), public java.lang.Object _22._22.super$1$clone(), public int _22._22.super$1$hashCode(), public void _22._22.super$1$finalize(), public java.lang.Object _22._22.this$dist$invoke$4(java.lang.String,java.lang.Object), public void _22._22.this$dist$set$4(java.lang.String,java.lang.Object), public java.lang.Object _22._22.this$dist$get$4(java.lang.String), public java.lang.Object _22._22.super$3$getProperty(java.lang.String), public void _22._22.super$3$setProperty(java.lang.String,java.lang.Object), public void _22._22.super$3$println(), public void _22._22.super$3$println(java.lang.Object), public void _22._22.super$3$print(java.lang.Object), public void _22._22.super$3$printf(java.lang.String,java.lang.Object), public void _22._22.super$3$printf(java.lang.String,java.lang.Object[]), public java.lang.Object _22._22.super$3$evaluate(java.lang.String), public java.lang.Object _22._22.super$3$evaluate(java.io.File), public groovy.lang.MetaClass _22._22.super$2$getMetaClass(), public void _22._22.super$2$setMetaClass(groovy.lang.MetaClass), public groovy.lang.Binding _22._22.super$3$getBinding(), public void _22._22.super$3$setBinding(groovy.lang.Binding), public void _22._22.super$3$run(java.io.File,java.lang.String[]), public java.lang.Object _22._22.super$3$invokeMethod(java.lang.String,java.lang.Object), public static void _22._22.main(java.lang.String[]), public java.lang.Object _22._22.run(), public void groovy.lang.Script.setBinding(groovy.lang.Binding), public java.lang.Object groovy.lang.Script.invokeMethod(java.lang.String,java.lang.Object), public groovy.lang.Binding groovy.lang.Script.getBinding(), public void groovy.lang.Script.println(), public void groovy.lang.Script.println(java.lang.Object), public void groovy.lang.Script.run(java.io.File,java.lang.String[]) throws org.codehaus.groovy.control.CompilationFailedException,java.io.IOException, public void groovy.lang.Script.setProperty(java.lang.String,java.lang.Object), public java.lang.Object groovy.lang.Script.getProperty(java.lang.String), public void groovy.lang.Script.print(java.lang.Object), public void groovy.lang.Script.printf(java.lang.String,java.lang.Object), public void groovy.lang.Script.printf(java.lang.String,java.lang.Object[]), public java.lang.Object groovy.lang.Script.evaluate(java.io.File) throws org.codehaus.groovy.control.CompilationFailedException,java.io.IOException, public java.lang.Object groovy.lang.Script.evaluate(java.lang.String) throws org.codehaus.groovy.control.CompilationFailedException, public groovy.lang.MetaClass groovy.lang.GroovyObjectSupport.getMetaClass(), public void groovy.lang.GroovyObjectSupport.setMetaClass(groovy.lang.MetaClass), public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
이연산자는 확산-도트 연산자에서 리스트의 각 요소로 대응해 주는 역활을 한다. 배열기호 연산자와는 반대라고 할수 있다 배열기호 연산자는 쉼표로 구분된 객체들을 모아서 리스트로 만들어준다. 확산 연산자는 리스트의 요소들을 수신자에게 배포해준다. 수신자는 여러 인자를 받을수 있는 메서드이거나 리스트를 생성할수있다. 어떤 메서드가 결과 값을 리스트에 담아서 리턴 하는데 호출한 측에서 이결과 값들을 다른 메서드에 전달해야 하는 상황을 생각해보자 확산 연산자를 이용하면 결과 값을 그대로 두번재 메서드에 인자로 전달할수있다. 아래와 같이 하면 여러개 리턴하는 메서드와 이를 수신하는 메서드들을 섞어 쓰기쉽고 수신하는 메서드에서는 인자로 따로 선언할수있다.
카테고리는 주어진 클로저와 현재 스레드에서만 동작한다 이런식의 변경이 전역적으로 반영되면 부작용이 발생되기때문이다. 1. 특수목적의 메서드를 제공한다 StringCalculation Category에서 본것처럼 연산 메서드의 대상이 동일한 클래스이고 기존 동작을 재정의해야 할 때 사용한다 예제처럼 연산자를 재정의하는 특수한 경우에도 쓸수있다. 2. 라이브러리 클래스에 메서드를 추가한다 ‘불안전 라이브러리 클래스’가 의심될때 사용하면 효과적이다. 3. 함께 동작하는 서로 다른 수신자 클래스를 위한 메서드 모음을 제공한다. 예를들어 java.io.OutputStream을 위한 encryptedWrite 메서드와 java.io.InputStream을 위한 encryptedWrite 메서드와 java.io.InputStream을 위한 decryptedRead메서드를 제공할수 있다. 3. 자바에서 Decorator 패턴을 써야 하는 겨웅에는 사용한다 하지만 메서드들을 많이 만들어야 하는 불편이 더는 겪지 않아도 된다. 4. 클래스가 너무 커졌을 때 이를 한개의 핵심 클래스와 상황에 맞는 여러개의 카테고리로 나눌수 있다 그리고 use에는 카테고리 클래스를 여러개줄수 있으므로 이카테고리들을 핵심 클래스와 함께 사용한다.
카테고리 메서드를 Object에 할당하면 그 메서드는 모든 객체에서 사용할수 있다. Object보다 더작은 영역에 적용하는 법에도 관심이 있을것이다 모든Collection 클래스나 여러분이 작성한 클래스들 중 특정 인터페이스를 공유하는 비즈니스 객체들 전부에 대해 적용할수도있다. use에는 카테고리 클래스 여러개를 줄수 있다 카테고리들은 쉼표로 불리하거나 리스트로줘도된다. use(a,b,c,d) use([a,b,c,d])..
그루비에는 가로챌수 있는 지점이 셀수 없이 많다 그중 어떤것을 고르는지에 따라 그루비가 내부적으로 제공하는 기능을 이용할수도 있고 재정의할 코드의 양이 달라지기도 한다. 특징들이 모여서 그루비의 메타오브젝트 프로토콜(MOP,Meta-Object Protocol)를 이룬다
그루비가 메서드를 호출하는 자바 바이트 코드를 생성할 때는 (몇차례 전달 과정을 거친후에) 다음중 한가지 방식으로 작성한다 1. 클래스 자체적인 invokeMethod 구현 호출(내부적으로 다른 MetaClass로 연결될수있다) 2. 자신의 MetaClass이용 getMetaClass().invokeMethod()호출 3. MetaClassRegisty에 저장된 객체의 자료형에 대한 MetaClass를 이용
위그림은 Groovy in Action 책에서 발췌하였습니다
1. 모든 메서드 호출을 중간에 가로챌(intercept) 수있다. 즉 모든 로깅(logging)이나 추적(tracing)을 가로채서 보안 관련 사항을 적용하고 트랜잭션 처리를 할수있다. 2. 다른객체로 호출을 전달할 (relay) 수있다 예를들어 감싸는 객체(wapper)가 자신이 처리하지 못하는 메서드를 감싸진(wrapped) 객체에게 전달하는 일이 가능하다 * 이게 클로저가 동작하는 방식이다 클로저는 메서드 호출을 자신의 대리인(delegate)에게 전달한다. 3. 사실 다른 일을 하면서 겉으로 메서드인척 가장할(pretend)수있다 예를들어 Html클래스는 body라는 메서드가 있는것처럼 보이면서 내부적으로는 print(‘body’) 를수행할수도있다. * 이것이 바로 빌더(builder)의 동작방식이다 빌더들은 중첩된 구조를 만들기 위해 메서드가 있는 척한다.
The delegate of a closure is an object that is used to resolve references that cannot be resolved within the body of the closure itself. If your example was written like this instead:
def say ={def m ='hello'
println m
}
say.delegate=[m:2]
say()
It prints 'hello', because m can be resolved within the closure. However, when m is not defined within the closure,
def say ={
println m
}
say.delegate=[m:2]
say()
the delegate is used to resolve the reference, and in this case the delegate is a Map that maps mto 2.
It is possible to add methods onto interfaces with ExpandoMetaClass. To do this however, it MUST be enabled globally using the ExpandoMetaClass.enableGlobally() method before application start-up.
As an example this code adds a new method to all implementors of java.util.List:
List.metaClass.sizeDoubled = {-> delegate.size() * 2 }
def list = []
list << 1
list << 2assert4 == list.sizeDoubled()
Another example taken from Grails, this code allows access to session attributes using Groovy's subscript operator to all implementors of the HttpSession interface:
그루비에서는 변수 선언시에 정적 또는 동적 타이핑이 가능하다. 변수 선언시에 java 처럼 변수 타입을 명시하는 방법을 정적 타이핑이라 하고 def 키워드를 사용해 선언하는 방식을 동적 타이핑이라고 한다. 동적 타이핑의 경우 런타임시에 그루비가 해당 변수의 타입을 결정한다.
Operator Overloading
그루비에서 모든 연산자는 메소드 호출로 해석된다. 예를 들어 1+1 은 1.plus(1) 이라는 식으로 해석되는 것이다. 따라서 이런 메소드를 오버로드하면 연산자도 함께 오버할 수 있다.