| name | patterns-metaprogramming-dev |
| description | Cross-cutting patterns for metaprogramming mechanisms across languages. Use when translating decorators between languages, converting annotations to macros, understanding metaprogramming equivalents, or designing code generation strategies for language conversions. |
Metaprogramming Patterns
Cross-language reference for metaprogramming mechanisms including decorators, macros, annotations, and code generation. This skill helps translate metaprogramming patterns between languages during code conversion.
Overview
This skill covers:
- Decorator/annotation/attribute comparison across languages
- Macro systems (compile-time vs runtime)
- Code generation patterns
- Translation strategies between paradigms
This skill does NOT cover:
- Language-specific metaprogramming tutorials (see
lang-*-devskills) - Building specific decorators/macros for applications
- Runtime reflection for debugging (see language-specific skills)
Metaprogramming Mechanism Comparison
| Language | Primary Mechanism | Execution Time | Power Level |
|---|---|---|---|
| TypeScript | Decorators | Runtime | Medium |
| Python | Decorators | Runtime | High |
| Rust | Proc macros, derive | Compile-time | Very High |
| Java/Kotlin | Annotations | Compile + Runtime | Medium |
| Go | //go:generate |
Build-time | Low |
| C# | Attributes | Runtime (reflection) | Medium |
| Ruby | Metaprogramming APIs | Runtime | Very High |
| Elixir | Macros | Compile-time | Very High |
Execution Time Impact
Compile-time (Rust, Elixir)
├── Zero runtime overhead
├── Full type information available
├── Complex transformations possible
└── Errors caught at compile time
Runtime (Python, TypeScript, Ruby)
├── Runtime overhead (usually minimal)
├── Dynamic behavior possible
├── Can inspect runtime values
└── Errors may occur at runtime
Build-time (Go generate)
├── Separate build step
├── Generates source files
├── No runtime mechanism
└── Manual regeneration needed
Decorator/Annotation Comparison
TypeScript Decorators
// Class decorator
@Controller('/users')
class UserController {
// Method decorator
@Get('/:id')
@Authorized(['admin'])
getUser(@Param('id') id: string): User {
return this.userService.find(id);
}
}
// Decorator factory (returns decorator)
function Log(prefix: string) {
return function (target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`${prefix}: ${key} called`);
return original.apply(this, args);
};
};
}
Capabilities:
- Class, method, property, parameter decorators
- Decorator factories for configuration
- Metadata reflection (
reflect-metadata) - Runtime execution (after class definition)
Python Decorators
from functools import wraps
# Function decorator
def log(prefix: str):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"{prefix}: {func.__name__} called")
return func(*args, **kwargs)
return wrapper
return decorator
# Class decorator
def singleton(cls):
instances = {}
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
pass
# Method with multiple decorators (applied bottom-up)
@app.route('/users/<id>')
@requires_auth
@log("API")
def get_user(id: str) -> User:
return user_service.find(id)
Capabilities:
- Function, method, class decorators
- Stacking multiple decorators
- Access to wrapped function's attributes
- Full runtime introspection
- Arbitrary Python code in decorators
Rust Derive Macros and Attributes
// Derive macro (generates trait implementations)
#[derive(Debug, Clone, Serialize, Deserialize)]
struct User {
#[serde(rename = "user_id")]
id: String,
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
}
// Attribute macro (transforms the item)
#[tokio::main]
async fn main() {
// ...
}
// Proc macro (custom compile-time code generation)
#[route(GET, "/users/:id")]
async fn get_user(id: Path<String>) -> impl Responder {
// Handler implementation
}
Capabilities:
- Derive macros for trait implementation
- Attribute macros for code transformation
- Function-like macros (
macro_rules!, proc macros) - Full AST access at compile time
- Zero runtime overhead
Java/Kotlin Annotations
// Runtime annotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cached {
int ttlSeconds() default 300;
}
// Usage
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
@Cached(ttlSeconds = 60)
public User getUser(@PathVariable String id) {
return userService.find(id);
}
}
Capabilities:
- Compile-time (
SOURCE), class-file (CLASS), runtime (RUNTIME) retention - Annotation processors for compile-time code generation
- Runtime reflection for reading annotations
- Limited to metadata (no code transformation)
Go Generate Directives
//go:generate stringer -type=Status
type Status int
const (
Pending Status = iota
Active
Completed
)
//go:generate mockgen -source=service.go -destination=mock_service.go
type UserService interface {
Find(id string) (*User, error)
}
Capabilities:
- Build-time code generation
- Executes external tools
- Generates new source files
- No runtime mechanism (just comments)
- Manual
go generatestep required
C# Attributes
[ApiController]
[Route("[controller]")]
public class UserController : ControllerBase
{
[HttpGet("{id}")]
[Authorize(Roles = "Admin")]
[ResponseCache(Duration = 60)]
public ActionResult<User> GetUser(string id)
{
return userService.Find(id);
}
}
// Custom attribute
[AttributeUsage(AttributeTargets.Method)]
public class LogAttribute : Attribute
{
public string Prefix { get; set; }
}
Capabilities:
- Runtime reflection to read attributes
- Compile-time analysis with Roslyn
- Source generators for code generation
- Metadata only (no direct code transformation)
Translation Patterns
Decorator → Derive Macro (TS/Python → Rust)
| Source Pattern | Rust Equivalent | Notes |
|---|---|---|
@Serialize |
#[derive(Serialize)] |
Derive macro |
@validate |
Validator crate derives | #[derive(Validate)] |
@log method decorator |
Tracing + custom wrapper | No direct equivalent |
@cache |
Memoization crate or manual | cached crate |
@singleton |
lazy_static! or OnceCell |
Different pattern |
Example Translation:
// TypeScript
@Entity()
class User {
@Column()
@Length(1, 100)
name: string;
@Column()
@IsEmail()
email: string;
}
// Rust equivalent
#[derive(Debug, Serialize, Deserialize, Validate)]
struct User {
#[validate(length(min = 1, max = 100))]
name: String,
#[validate(email)]
email: String,
}
Decorator → Annotation (Python/TS → Java)
# Python
@app.route('/users/<id>', methods=['GET'])
@requires_auth
def get_user(id: str) -> User:
return user_service.find(id)
// Java equivalent
@GetMapping("/users/{id}")
@PreAuthorize("isAuthenticated()")
public User getUser(@PathVariable String id) {
return userService.find(id);
}
Method Decorator → Manual Wrapper (Any → Go)
Go lacks decorators. Use wrapper functions or middleware:
// TypeScript
@Log("API")
@Timed()
async getUser(id: string): Promise<User> {
return this.service.find(id);
}
// Go equivalent - middleware pattern
func LogMiddleware(prefix string, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s: %s %s", prefix, r.Method, r.URL.Path)
next(w, r)
}
}
func TimedMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next(w, r)
log.Printf("Duration: %v", time.Since(start))
}
}
// Usage
http.HandleFunc("/users/", LogMiddleware("API", TimedMiddleware(getUser)))
Class Decorator → Trait Implementation (Python → Rust)
# Python
@dataclass
@total_ordering
class User:
name: str
age: int
// Rust equivalent
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct User {
name: String,
age: i32,
}
Common Use Cases
1. Serialization/Validation
| Use Case | TypeScript | Python | Rust | Go |
|---|---|---|---|---|
| JSON serialization | class-transformer |
@dataclass + json |
#[derive(Serialize)] |
struct tags |
| Validation | class-validator |
pydantic |
#[derive(Validate)] |
validator pkg |
| ORM mapping | TypeORM decorators | SQLAlchemy | Diesel derives | GORM tags |
2. Web Frameworks
| Framework Pattern | TypeScript | Python | Rust | Java |
|---|---|---|---|---|
| Route definition | @Get('/path') |
@app.route() |
#[get("/path")] |
@GetMapping |
| Dependency injection | @Injectable() |
@inject |
Constructor | @Autowired |
| Middleware | @UseGuards() |
@requires_auth |
Tower layers | @PreAuthorize |
3. Logging/Tracing
| Pattern | TypeScript | Python | Rust | Go |
|---|---|---|---|---|
| Method logging | @Log() |
@log |
#[instrument] |
Middleware |
| Timing | @Timed() |
@timer |
#[instrument] |
Middleware |
| Tracing | OpenTelemetry decorators | @trace |
tracing macros |
Context |
Anti-Patterns
1. Over-decoration
// ❌ Too many decorators obscure the logic
@Controller()
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
@UsePipes(ValidationPipe)
@UseFilters(HttpExceptionFilter)
class UserController {
@Get()
@UseGuards(RoleGuard)
@Serialize(UserDto)
@Cache(60)
@Throttle(10, 60)
@ApiOperation({ summary: 'Get users' })
@ApiResponse({ status: 200 })
getUsers() { }
}
// ✓ Group related concerns
@Controller()
@UseGuards(AuthGuard, RoleGuard)
class UserController {
@Get()
@Cache(60)
getUsers() { }
}
2. Side Effects in Decorators
# ❌ Decorator with hidden side effects
def register(func):
global_registry.append(func) # Hidden mutation!
return func
# ✓ Explicit registration
def register(func):
func._registered = True
return func
def collect_registered(module):
return [f for f in dir(module) if getattr(f, '_registered', False)]
3. Decorator Order Confusion
# Decorators apply bottom-up!
@decorator_a # Applied SECOND
@decorator_b # Applied FIRST
def func():
pass
# Equivalent to:
func = decorator_a(decorator_b(func))
4. Losing Function Metadata
# ❌ Loses original function name, docstring
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
# ✓ Preserve metadata
from functools import wraps
def good_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
When No Direct Equivalent Exists
Strategy 1: Manual Implementation
When target language lacks metaprogramming for a pattern, implement manually:
// TypeScript: Memoization decorator
@Memoize()
function expensiveComputation(n: number): number {
// ...
}
// Go: Manual memoization
var cache = make(map[int]int)
var mu sync.RWMutex
func expensiveComputation(n int) int {
mu.RLock()
if val, ok := cache[n]; ok {
mu.RUnlock()
return val
}
mu.RUnlock()
result := // ... compute
mu.Lock()
cache[n] = result
mu.Unlock()
return result
}
Strategy 2: Code Generation
Use build-time generation when runtime metaprogramming isn't available:
//go:generate go run gen_memoize.go -type=expensiveComputation
Strategy 3: Interface/Trait Abstraction
Replace decorator behavior with explicit interfaces:
// TypeScript: Decorator-based
@Injectable()
class UserService {
@Transactional()
async createUser(data: UserData): Promise<User> { }
}
// Rust: Trait-based
trait Transactional {
async fn in_transaction<F, T>(&self, f: F) -> Result<T>
where
F: FnOnce() -> Result<T>;
}
impl UserService {
async fn create_user(&self, data: UserData) -> Result<User> {
self.in_transaction(|| {
// ... implementation
}).await
}
}
Best Practices
- Prefer compile-time over runtime when possible for performance
- Keep decorators focused - one responsibility per decorator
- Document decorator behavior - especially execution order
- Preserve function metadata - use
@wrapsin Python, etc. - Consider testability - decorated code should remain testable
- Avoid magic - decorator behavior should be predictable
- Match target language idioms - don't force patterns that don't fit
Related Skills
meta-convert-dev- Code conversion patternsconvert-*skills - Language-specific conversionslang-*-devskills - Language-specific metaprogramming detailspatterns-serialization-dev- Serialization patterns (often uses metaprogramming)