# Utilities and Transformations

Helper functions for converting between mutable and immutable structures, applying transformations, and accessing nested data. These utilities bridge the gap between regular Python data structures and persistent collections.

## Capabilities

### Freeze/Thaw Conversions

Convert between mutable Python built-in types and persistent collections, enabling easy integration with existing codebases.

```python { .api }
def freeze(obj):
    """
    Recursively convert mutable Python structures to persistent equivalents.
    
    Conversions:
    - dict -> pmap
    - list -> pvector  
    - set -> pset
    - tuple -> tuple (preserved)
    - Other types -> unchanged
    
    Parameters:
    - obj: Object to convert
    
    Returns:
    Persistent version of the input structure
    """

def thaw(obj):
    """
    Recursively convert persistent structures to mutable Python equivalents.
    
    Conversions:
    - pmap -> dict
    - pvector -> list
    - pset -> set
    - tuple -> tuple (preserved)
    - Other types -> unchanged
    
    Parameters:
    - obj: Object to convert
    
    Returns:
    Mutable version of the input structure
    """

def mutant(fn) -> callable:
    """
    Decorator that automatically freezes function arguments and return value.
    
    Useful for integrating persistent data structures with functions that
    expect mutable inputs or for ensuring immutability of function results.
    
    Parameters:
    - fn: Function to decorate
    
    Returns:
    Decorated function that freezes args and return value
    """
```

### Nested Data Access

Safely access and manipulate nested data structures using key paths.

```python { .api }
def get_in(keys: Iterable, coll: Mapping, default=None, no_default: bool = False):
    """
    Get value from nested mapping structure using sequence of keys.
    
    Equivalent to coll[keys[0]][keys[1]]...[keys[n]] with safe fallback.
    
    Parameters:
    - keys: Sequence of keys for nested access
    - coll: Nested mapping structure to access
    - default: Value to return if path doesn't exist
    - no_default: If True, raise KeyError instead of returning default
    
    Returns:
    Value at the nested path, or default if path doesn't exist
    
    Raises:
    KeyError: If path doesn't exist and no_default=True
    """
```

### Transformation Functions

Functions for use with the `.transform()` method of persistent collections to apply path-based modifications.

```python { .api }
def inc(x: int) -> int:
    """
    Increment numeric value by 1.
    
    Transformation function for use with .transform().
    
    Parameters:
    - x: Numeric value to increment
    
    Returns:
    x + 1
    """

def discard(evolver, key) -> None:
    """
    Remove element from evolver during transformation.
    
    Transformation function that removes a key/element from the
    collection being transformed.
    
    Parameters:
    - evolver: Collection evolver (PMapEvolver, PVectorEvolver, PSetEvolver)
    - key: Key/index/element to remove
    """

def rex(expr: str) -> callable:
    """
    Create regex matcher for transformation paths.
    
    Returns a function that tests if a string matches the regex pattern.
    Useful for selecting which keys/paths to transform.
    
    Parameters:
    - expr: Regular expression pattern
    
    Returns:
    Function that tests strings against the regex
    """

def ny(_) -> bool:
    """
    Matcher that always returns True.
    
    Useful as a catch-all matcher in transformations when you want
    to match any value.
    
    Parameters:
    - _: Any value (ignored)
    
    Returns:
    True (always)
    """
```

### Immutable Factory

Create namedtuple-like immutable classes with optional field validation.

```python { .api }
def immutable(
    members: Union[str, Iterable[str]] = '', 
    name: str = 'Immutable', 
    verbose: bool = False
) -> type:
    """
    Create an immutable namedtuple-like class.
    
    Creates a class similar to collections.namedtuple but with additional
    immutability guarantees and optional field validation.
    
    Parameters:
    - members: Field names as string (space/comma separated) or iterable
    - name: Name for the created class
    - verbose: If True, print the generated class definition
    
    Returns:
    Immutable class type with specified fields
    """
```

## Usage Examples

### Freeze/Thaw Conversions

```python
from pyrsistent import freeze, thaw, pmap, pvector

# Convert nested mutable structures to persistent
mutable_data = {
    'users': [
        {'name': 'Alice', 'tags': {'admin', 'active'}},
        {'name': 'Bob', 'tags': {'user', 'active'}}
    ],
    'config': {
        'debug': True,
        'features': ['auth', 'logging']
    }
}

# Recursively convert to persistent structures
persistent_data = freeze(mutable_data)
# Result: pmap({
#     'users': pvector([
#         pmap({'name': 'Alice', 'tags': pset(['admin', 'active'])}),
#         pmap({'name': 'Bob', 'tags': pset(['user', 'active'])})
#     ]),
#     'config': pmap({
#         'debug': True, 
#         'features': pvector(['auth', 'logging'])
#     })
# })

# Convert back to mutable for JSON serialization or external APIs
mutable_again = thaw(persistent_data)
import json
json_str = json.dumps(mutable_again)
```

### Mutant Decorator

```python
from pyrsistent import mutant, pmap

@mutant
def process_user_data(data):
    """
    Function that expects and returns mutable data, but we want to
    use persistent structures internally for safety.
    """
    # data is automatically frozen (converted to persistent)
    # Work with persistent data safely
    if 'email' not in data:
        data = data.set('email', '')
    
    data = data.set('processed', True)
    
    # Return value is automatically frozen
    return data

# Use with mutable input - automatically converted
user_dict = {'name': 'Alice', 'age': 30}
result = process_user_data(user_dict)
# result is a pmap, input dict is unchanged
```

### Nested Access

```python
from pyrsistent import get_in, pmap, pvector

# Complex nested structure
data = pmap({
    'api': pmap({
        'v1': pmap({
            'endpoints': pvector([
                pmap({'path': '/users', 'methods': pvector(['GET', 'POST'])}),
                pmap({'path': '/posts', 'methods': pvector(['GET', 'POST', 'DELETE'])})
            ])
        })
    }),
    'config': pmap({
        'database': pmap({
            'host': 'localhost',
            'port': 5432
        })
    })
})

# Safe nested access
db_host = get_in(['config', 'database', 'host'], data)  # 'localhost'
api_endpoints = get_in(['api', 'v1', 'endpoints'], data)  # pvector([...])
missing = get_in(['config', 'cache', 'ttl'], data, default=300)  # 300

# Access with index for vectors
first_endpoint = get_in(['api', 'v1', 'endpoints', 0, 'path'], data)  # '/users'

# Raise error if path doesn't exist
try:
    get_in(['nonexistent', 'path'], data, no_default=True)
except KeyError:
    print("Path not found")
```

### Transformations

```python
from pyrsistent import pmap, pvector, inc, discard, rex, ny

# Apply transformations to nested structures
data = pmap({
    'counters': pmap({'page_views': 100, 'api_calls': 50}),
    'users': pvector(['alice', 'bob', 'charlie']),
    'temp_data': 'to_be_removed'
})

# Increment all counters
transformed = data.transform(
    ['counters', ny], inc  # For any key in counters, apply inc function
)
# Result: counters become {'page_views': 101, 'api_calls': 51}

# Remove elements matching pattern
transformed2 = data.transform(
    [rex(r'temp_.*')], discard  # Remove any key matching temp_*
)
# Result: 'temp_data' key is removed

# Complex transformation combining multiple operations
def process_user(user):
    return user.upper() if isinstance(user, str) else user

transformed3 = data.transform(
    ['users', ny], process_user,  # Transform all users
    ['counters', 'page_views'], lambda x: x * 2,  # Double page views
    ['temp_data'], discard  # Remove temp data
)
```

### Immutable Classes

```python
from pyrsistent import immutable

# Create immutable point class
Point = immutable('x y', name='Point')
p1 = Point(x=1, y=2)
p2 = Point(3, 4)  # Positional args also work

print(p1.x, p1.y)  # 1 2
print(p1)  # Point(x=1, y=2)

# Immutable - cannot modify
try:
    p1.x = 5  # Raises AttributeError
except AttributeError:
    print("Cannot modify immutable object")

# Create new instances with _replace
p3 = p1._replace(x=10)  # Point(x=10, y=2)

# With more complex fields
Person = immutable('name age email', name='Person')
person = Person('Alice', 30, 'alice@example.com')

# Support for tuple unpacking
name, age, email = person
```

### Integration Patterns

```python
from pyrsistent import freeze, thaw, pmap
import json

# Working with JSON APIs
def load_config(filename):
    """Load configuration from JSON file into persistent structure."""
    with open(filename) as f:
        mutable_config = json.load(f)
    return freeze(mutable_config)

def save_config(config, filename):
    """Save persistent configuration to JSON file."""
    with open(filename, 'w') as f:
        json.dump(thaw(config), f, indent=2)

# Thread-safe configuration management
class ConfigManager:
    def __init__(self, initial_config):
        self._config = freeze(initial_config)
    
    def get_config(self):
        return self._config  # Safe to share between threads
    
    def update_config(self, updates):
        # Atomic update - no race conditions
        self._config = self._config.update(freeze(updates))
    
    def get_setting(self, *path):
        return get_in(path, self._config)

# Usage
config_mgr = ConfigManager({'database': {'host': 'localhost', 'port': 5432}})
db_host = config_mgr.get_setting('database', 'host')
config_mgr.update_config({'database': {'timeout': 30}})
```