템플릿 믹스인
템플릿 믹스인 (Template Mixins)
템플릿 믹스인(Template Mixin)은 템플릿 선언의 본문에 담긴 임의의 선언 묶음을 그대로 가져와서, 현재 스코프에 삽입하는 D 언어의 기능이에요. 일반적인 템플릿 인스턴스화와 달리 선언이 정의된 곳이 아니라 믹스인이 놓인 곳에서 본문이 평가되기 때문에, 매개변수화된 '보일러플레이트' 코드를 주입하거나 템플릿화된 중첩 함수를 만들 때 특히 유용해요.
본문
템플릿 믹스인(TemplateMixin)은 템플릿 선언(TemplateDeclaration)의 본문에 있는 임의의 선언 묶음을 가져와서, 현재 컨텍스트에 그대로 삽입하는 기능이에요.
TemplateMixinDeclaration:
mixin template Identifier TemplateParameters Constraintopt { DeclDefsopt }
TemplateMixin:
mixin MixinTemplateName TemplateArgumentsopt Identifieropt ;
mixin Identifier = MixinTemplateName TemplateArgumentsopt ;
MixinTemplateName:
. MixinQualifiedIdentifier
MixinQualifiedIdentifier
Typeof . MixinQualifiedIdentifier
MixinQualifiedIdentifier:
Identifier
Identifier . MixinQualifiedIdentifier
TemplateInstance . MixinQualifiedIdentifier
템플릿 믹스인은 모듈, 클래스, 구조체, 공용체(union)의 선언 목록 어디에도 올 수 있고, 하나의 문장(statement)으로도 쓸 수 있어요. 이때 MixinTemplateName은 반드시 TemplateDeclaration이나 TemplateMixinDeclaration을 가리켜야 해요. 그리고 템플릿 선언이 매개변수를 요구하지 않는다면 TemplateArguments는 생략할 수 있어요.
일반적인 템플릿 인스턴스화(template instantiation)와 달리, 템플릿 믹스인의 본문은 템플릿 선언이 정의된 곳이 아니라 믹스인이 등장한 스코프 안에서 평가돼요. 마치 템플릿 본문을 복사해서 믹스인이 있는 자리(중첩 스코프)에 붙여넣은 것과 비슷하죠. 이 덕분에 매개변수화된 '보일러플레이트' 코드를 주입하기도 좋고, 템플릿 인스턴스화로는 항상 만들기 어려운 템플릿화된 중첩 함수를 만들 때도 유용해요.
TemplateMixinDeclaration은 TemplateDeclaration과 같지만, TemplateMixin 밖에서는 인스턴스화할 수 없다는 점이 달라요.
int y = 3;
mixin template Foo()
{
int abc() { return y; }
}
void test()
{
int y = 8;
mixin Foo; // local y is picked up, not global y
assert(abc() == 8);
}
import std.stdio : writeln;
mixin template Foo()
{
int x = 5;
}
mixin Foo;
struct Bar
{
mixin Foo;
}
void main()
{
writeln("x = ", x); // prints 5
{
Bar b;
int x = 3;
writeln("b.x = ", b.x); // prints 5
writeln("x = ", x); // prints 3
{
mixin Foo;
writeln("x = ", x); // prints 5
x = 4;
writeln("x = ", x); // prints 4
}
writeln("x = ", x); // prints 3
}
writeln("x = ", x); // prints 5
}
믹스인 매개변수 (Mixin Parameters)
믹스인도 매개변수화할 수 있어요.
mixin template Foo(T)
{
T x = 5;
}
mixin Foo!(int); // create x of type int
alias 매개변수를 이용해서 심볼 자체를 매개변수화할 수도 있어요.
mixin template Foo(alias b)
{
int abc() { return b; }
}
void test()
{
int y = 8;
mixin Foo!(y);
assert(abc() == 8);
}
예시 (Example)
이 예시는 임의의 문장을 처리하는 범용 Duff's device를 믹스인으로 구현해요(여기서 임의의 문장은 굵게 표시된 부분이에요). 중첩 함수와 delegate 리터럴이 함께 생성되는데, 둘 다 컴파일러가 인라인할 수 있어요.
import std.stdio : writeln;
mixin template duffs_device(alias low, alias high, alias fun)
{
void duff_loop()
{
if (low < high)
{
auto n = (high - low + 7) / 8;
switch ((high - low) % 8)
{
case 0: do { fun(); goto case;
case 7: fun(); goto case;
case 6: fun(); goto case;
case 5: fun(); goto case;
case 4: fun(); goto case;
case 3: fun(); goto case;
case 2: fun(); goto case;
case 1: fun(); continue;
default: assert(0, "Impossible");
} while (--n > 0);
}
}
}
}
void main()
{
int i = 1;
int j = 11;
mixin duffs_device!(i, j, delegate { writeln("foo"); });
duff_loop(); // executes foo() 10 times
}
믹스인 스코프 (Mixin Scope)
믹스인의 선언들은 중첩 스코프에 놓인 다음, 바깥 스코프로 '수입(import)'돼요. 만약 믹스인 안의 선언 이름이 바깥 스코프의 선언과 같다면, 바깥 쪽 선언이 믹스인의 선언을 덮어써요.
import std.stdio : writeln;
int x = 3;
mixin template Foo()
{
int x = 5;
int y = 5;
}
mixin Foo;
int y = 3;
void main()
{
writeln("x = ", x); // prints 3
writeln("y = ", y); // prints 3
}
믹스인은 바깥 선언이 자신을 덮어쓰더라도 자기만의 스코프를 따로 가져요.
import std.stdio : writeln;
int x = 4;
mixin template Foo()
{
int x = 5;
int bar() { return x; }
}
mixin Foo;
void main()
{
writeln("x = ", x); // prints 4
writeln("bar() = ", bar()); // prints 5
}
서로 다른 두 믹스인을 같은 스코프에 넣었는데 각각 같은 이름의 선언을 정의한다면, 그 선언을 참조할 때 모호성(ambiguity) 오류가 나요.
import std.stdio : writeln;
mixin template Foo()
{
int x = 5;
void func(int x) { }
}
mixin template Bar()
{
int x = 4;
void func(long x) { }
}
mixin Foo;
mixin Bar;
void main()
{
writeln("x = ", x); // error, x is ambiguous
func(1); // error, func is ambiguous
}
func() 호출이 모호한 이유는 Foo.func와 Bar.func이 서로 다른 스코프에 있기 때문이에요.
모호성 해소 (Resolving Ambiguities)
믹스인에 Identifier를 붙여두면, 그 이름으로 충돌하는 심볼들을 구분할 수 있어요.
import std.stdio : writeln;
int x = 6;
mixin template Foo()
{
int x = 5;
int y = 7;
void func() { }
}
mixin template Bar()
{
int x = 4;
void func() { }
}
mixin Foo F;
mixin Bar B;
void main()
{
writeln("y = ", y); // prints 7
writeln("x = ", x); // prints 6
writeln("F.x = ", F.x); // prints 5
writeln("B.x = ", B.x); // prints 4
F.func(); // calls Foo.func
B.func(); // calls Bar.func
}
alias 선언을 이용하면 서로 다른 믹스인에 선언된 함수들을 하나의 오버로드 집합(overload set)으로 묶을 수도 있어요.
mixin template Foo()
{
void func(int x) { }
}
mixin template Bar()
{
void func(long x) { }
}
mixin Foo!() F;
mixin Bar!() B;
alias func = F.func;
alias func = B.func;
void main()
{
func(1); // calls B.func
func(1L); // calls F.func
}
집합 타입 믹스인 (Aggregate Type Mixins)
믹스인 가상 함수 (Mixin Virtual Functions)
믹스인은 클래스에 가상 함수(virtual function)를 추가할 수 있어요.
import std.stdio : writeln;
mixin template Foo()
{
void func() { writeln("Foo.func()"); }
}
class Bar
{
mixin Foo;
}
class Code : Bar
{
override void func() { writeln("Code.func()"); }
}
void main()
{
Bar b = new Bar();
b.func(); // calls Foo.func()
b = new Code();
b.func(); // calls Code.func()
}
믹스인 소멸자 (Mixin Destructors)
집합 타입(aggregate type)에는 추가 소멸자(destructor)를 믹스인할 수 있어요. 소멸자들은 선언된 순서와 반대 순서로 실행돼요.
import std.stdio;
mixin template addNewDtor()
{
~this()
{
writeln("Mixin dtor");
}
}
struct S
{
~this()
{
writeln("Struct dtor");
}
mixin addNewDtor;
}
void main()
{
S s;
// prints `Mixin dtor`
// prints `Struct dtor`
}
더 알아보기 (Learn more)
- 템플릿 (Templates) — https://dlang.org/spec/template.html — 템플릿 선언, 인스턴스화, 매개변수 종류(
alias, 값, 타입) 등 믹스인의 기초가 되는 템플릿 전반을 다뤄요. - 계약 프로그래밍 (Contract Programming) — https://dlang.org/spec/contracts.html — 다음 챕터로, 함수·클래스의 전제/사후 조건과 불변식을 다뤄요.
- D 언어 레퍼런스 목차 — https://dlang.org/spec/spec.html — 전체 사양 챕터 모음.