home / skills / arustydev / ai / patterns-metaprogramming-dev

patterns-metaprogramming-dev skill

/components/skills/patterns-metaprogramming-dev

This skill helps you map metaprogramming patterns across languages, translating decorators, annotations, and macros to guide code generation and migrations.

npx playbooks add skill arustydev/ai --skill patterns-metaprogramming-dev

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
13.5 KB
---
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-*-dev` skills)
- 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

```typescript
// 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

```python
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

```rust
// 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

```java
// 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
//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 generate` step required

### C# Attributes

```csharp
[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
// TypeScript
@Entity()
class User {
  @Column()
  @Length(1, 100)
  name: string;

  @Column()
  @IsEmail()
  email: string;
}
```

```rust
// 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
# Python
@app.route('/users/<id>', methods=['GET'])
@requires_auth
def get_user(id: str) -> User:
    return user_service.find(id)
```

```java
// 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
// TypeScript
@Log("API")
@Timed()
async getUser(id: string): Promise<User> {
    return this.service.find(id);
}
```

```go
// 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
# Python
@dataclass
@total_ordering
class User:
    name: str
    age: int
```

```rust
// 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

```typescript
// ❌ 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

```python
# ❌ 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

```python
# 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

```python
# ❌ 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
// TypeScript: Memoization decorator
@Memoize()
function expensiveComputation(n: number): number {
    // ...
}
```

```go
// 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
//go:generate go run gen_memoize.go -type=expensiveComputation
```

### Strategy 3: Interface/Trait Abstraction

Replace decorator behavior with explicit interfaces:

```typescript
// TypeScript: Decorator-based
@Injectable()
class UserService {
    @Transactional()
    async createUser(data: UserData): Promise<User> { }
}
```

```rust
// 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

1. **Prefer compile-time over runtime** when possible for performance
2. **Keep decorators focused** - one responsibility per decorator
3. **Document decorator behavior** - especially execution order
4. **Preserve function metadata** - use `@wraps` in Python, etc.
5. **Consider testability** - decorated code should remain testable
6. **Avoid magic** - decorator behavior should be predictable
7. **Match target language idioms** - don't force patterns that don't fit

---

## Related Skills

- `meta-convert-dev` - Code conversion patterns
- `convert-*` skills - Language-specific conversions
- `lang-*-dev` skills - Language-specific metaprogramming details
- `patterns-serialization-dev` - Serialization patterns (often uses metaprogramming)

Overview

This skill catalogs cross-cutting metaprogramming patterns and maps equivalents across languages. It helps you translate decorators, annotations, macros, and code-generation tactics when converting code or designing interoperability strategies. The focus is practical: what exists in each ecosystem and how to achieve similar behavior when direct features differ.

How this skill works

I compare common metaprogramming primitives (decorators, attributes, macros, code gen directives) across languages and outline execution-time tradeoffs (compile-time, build-time, runtime). For each pattern I show typical capabilities, translation strategies, and anti-patterns, then recommend implementation alternatives when no direct equivalent exists. The guidance highlights concrete techniques like derive macros, middleware wrappers, build-time generators, and trait/interface abstractions.

When to use it

  • Translating decorators/annotations between languages (e.g., TS/Python → Rust/Java)
  • Designing code generation strategies for language conversions
  • Choosing between compile-time macros, runtime decorators, or build-time generation
  • Replacing missing metaprogramming features with idiomatic patterns
  • Auditing cross-language conversions for performance and correctness tradeoffs

Best practices

  • Prefer compile-time mechanisms when possible to avoid runtime overhead
  • Keep each decorator/macro focused on a single responsibility
  • Document execution order and side effects clearly
  • Preserve function metadata and testability when wrapping behavior
  • Match the target language idioms instead of forcing source patterns

Example use cases

  • Convert TypeScript route decorators to Rust attribute macros or Java annotations during a migration
  • Replace Python method decorators with Go middleware or wrapper functions where Go lacks decorators
  • Translate validation/serialization decorators to Rust derive macros and attribute-level validation
  • Implement memoization in a language without decorators using code generation or manual caches
  • Design a cross-language strategy for logging/tracing using source-level instrumentation or middleware

FAQ

What if the target language has no decorator or macro system?

Use manual wrappers, middleware patterns, explicit interfaces/traits, or build-time code generation to reproduce behavior in an idiomatic way.

When should I prefer code generation over runtime decorators?

Prefer code generation when you need zero runtime overhead, strong type information, or complex transformations that are safer at compile time.

How do I avoid hidden side effects in decorators?

Make registrations explicit, avoid global mutations inside decorators, and prefer marking/collecting metadata rather than silently mutating global state.