fugue-ppl 0.2.0

Monadic PPL with numerically stable inference and comprehensive diagnostics.
Documentation
# `macros` module

## Overview

The macros module provides convenient syntactic sugar for writing probabilistic programs in Fugue. These macros make it easier to express complex probabilistic computations using familiar programming constructs.

## Available Macros

### `prob!` - Probabilistic Programming Notation

The `prob!` macro provides Haskell-style do-notation for probabilistic programming, making it easier to chain probabilistic computations.

**Syntax:**

- `let var <- expr` - Sample from a probabilistic computation
- `let var = expr` - Regular variable assignment
- `expr` - Final return value

**Example:**

```rust
use fugue::*;

let model = prob! {
    let x <- sample(addr!("x"), Normal::new(0.0, 1.0).unwrap());
    let y <- sample(addr!("y"), Normal::new(x, 1.0).unwrap());
    let z = x + y;  // Regular assignment
    pure(z)
};
```

### `plate!` - Vectorized Operations

The `plate!` macro implements plate notation for replicating probabilistic computations over ranges or collections.

**Syntax:**

- `plate!(var in range => body)` - Execute `body` for each element in `range`

**Example:**

```rust
use fugue::*;

// Sample 10 independent normal variables
let model = plate!(i in 0..10 => {
    sample(addr!("x", i), Normal::new(0.0, 1.0).unwrap())
});

// With observations - using move to capture i
let model = plate!(i in 0..3 => {
    sample(addr!("mu", i), Normal::new(0.0, 1.0).unwrap())
        .bind(move |mu| observe(addr!("obs", i), Normal::new(mu, 0.5).unwrap(), 1.0 + i as f64))
});
```

### `scoped_addr!` - Hierarchical Addresses

The `scoped_addr!` macro creates hierarchical addresses for organizing model parameters.

**Syntax:**

- `scoped_addr!(scope, name)` - Creates "scope::name"
- `scoped_addr!(scope, name, format, args...)` - Creates "scope::name#formatted"

**Example:**

```rust
use fugue::*;

// Simple scoped address
let addr1 = scoped_addr!("layer1", "weight");  // "layer1::weight"

// With indices
let addr2 = scoped_addr!("layer1", "weight", "{}", 0);  // "layer1::weight#0"
let addr3 = scoped_addr!("layer1", "bias", "{}_{}", 2, 3);  // "layer1::bias#2_3"
```

## Common Patterns

### Hierarchical Models with Plate Notation

```rust
use fugue::*;

// Simple hierarchical model example
let n_groups = 3;
let model = prob! {
    // Global hyperparameters
    let global_mu <- sample(addr!("global_mu"), Normal::new(0.0, 10.0).unwrap());

    // Group-level parameters
    let group_means <- plate!(g in 0..n_groups => {
        sample(scoped_addr!("group", "mu", "{}", g),
               Normal::new(global_mu, 1.0).unwrap())
    });

    pure(group_means)
};
```

### Sequential Models

```rust
use fugue::*;

// Simple sequential model example
let model = prob! {
    // Sample parameters
    let states <- plate!(t in 0..3 => {
        sample(addr!("x", t), Normal::new(0.0, 1.0).unwrap())
            .bind(move |x_t| {
                observe(addr!("y", t), Normal::new(x_t, 0.5).unwrap(), 1.0 + t as f64)
                    .map(move |_| x_t)
            })
    });

    pure(states)
};
```

## Best Practices

1. **Use descriptive addresses**: Make your model structure clear through well-named addresses
2. **Scope your addresses**: Use `scoped_addr!` for complex hierarchical models
3. **Leverage plate notation**: Use `plate!` for vectorized operations rather than manual loops
4. **Mix syntax styles**: Combine `prob!` with regular function calls for complex models

## Integration

**Related Modules:**

- [`core`]../core/README.md: Core model and distribution types used in macros
- [`runtime`]../runtime/README.md: Execution of models created with macros

**See Also:**

- Main documentation: [API docs]https://docs.rs/fugue