# Type-Checked Collections

Runtime type validation for persistent collections with optional invariant checking. These collections provide the same functionality as core collections but with automatic type checking and validation.

## Capabilities

### CheckedPMap - Type-Checked Persistent Map

Persistent map with runtime validation of key and value types. Raises CheckedKeyTypeError or CheckedValueTypeError when invalid types are used.

```python { .api }
class CheckedPMap(PMap):
    """
    Type-checked persistent map with key and value type validation.
    
    Class attributes:
    - __key_type__: Required key type or tuple of allowed types
    - __value_type__: Required value type or tuple of allowed types
    """
    
    __key_type__: type
    __value_type__: type
    
    def __new__(cls, source: Mapping = {}, size: int = 0) -> 'CheckedPMap': ...
    
    @classmethod
    def create(cls, source_data: Mapping, _factory_fields=None) -> 'CheckedPMap':
        """Create instance with type validation."""
    
    def serialize(self, format=None) -> dict:
        """Serialize to regular dict."""
```

### CheckedPVector - Type-Checked Persistent Vector

Persistent vector with runtime validation of element types.

```python { .api }
class CheckedPVector(PVector):
    """
    Type-checked persistent vector with element type validation.
    
    Class attributes:
    - __type__: Required element type or tuple of allowed types
    """
    
    __type__: type
    
    def __new__(cls, initial: Iterable = ()) -> 'CheckedPVector': ...
    
    @classmethod
    def create(cls, source_data: Iterable, _factory_fields=None) -> 'CheckedPVector':
        """Create instance with type validation."""
    
    def serialize(self, format=None) -> list:
        """Serialize to regular list."""
```

### CheckedPSet - Type-Checked Persistent Set

Persistent set with runtime validation of element types.

```python { .api }
class CheckedPSet(PSet):
    """
    Type-checked persistent set with element type validation.
    
    Class attributes:
    - __type__: Required element type or tuple of allowed types
    """
    
    __type__: type
    
    def __new__(cls, initial: Iterable = ()) -> 'CheckedPSet': ...
    
    @classmethod
    def create(cls, source_data: Iterable, _factory_fields=None) -> 'CheckedPSet':
        """Create instance with type validation."""
    
    def serialize(self, format=None) -> set:
        """Serialize to regular set."""
```

### CheckedType Base Class

Abstract base class for all type-checked collections providing common validation infrastructure.

```python { .api }
class CheckedType:
    """Abstract base class for type-checked collections."""
    
    @classmethod
    def create(cls, source_data, _factory_fields=None):
        """Factory method for creating instances with validation."""
    
    def serialize(self, format=None):
        """Serialize to corresponding Python built-in type."""
```

## Type Specification Functions

### Optional Types

Allow a field or collection to accept specified types or None.

```python { .api }
def optional(*types) -> tuple:
    """
    Create a type specification that allows specified types or None.
    
    Parameters:
    - *types: Types to allow (in addition to None)
    
    Returns:
    Tuple of types including None
    
    Example:
    optional(int, str) -> (int, str, type(None))
    """
```

## Exception Classes

Type checking exceptions raised when validation fails:

```python { .api }
class InvariantException(Exception):
    """
    Raised when invariant validation fails.
    
    Attributes:
    - invariant_errors: Tuple of validation error messages
    - missing_fields: Tuple of missing required field names
    """
    
    invariant_errors: tuple
    missing_fields: tuple

class CheckedTypeError(TypeError):
    """
    Base exception for type validation errors in checked collections.
    
    Attributes:
    - source_class: Class that raised the error
    - expected_types: Tuple of expected types
    - actual_type: Actual type that was provided
    - actual_value: The value that caused the error
    """
    
    source_class: type
    expected_types: tuple
    actual_type: type
    actual_value: object

class CheckedKeyTypeError(CheckedTypeError):
    """Raised when CheckedPMap receives invalid key type."""

class CheckedValueTypeError(CheckedTypeError):
    """Raised when CheckedPMap/CheckedPVector/CheckedPSet receives invalid value/element type."""
```

## Usage Examples

### Basic Type Checking

```python
from pyrsistent import CheckedPMap, CheckedPVector, CheckedPSet, optional

# Define a type-checked map for string keys and integer values
class StringIntMap(CheckedPMap):
    __key_type__ = str
    __value_type__ = int

# Create and use the map
sim = StringIntMap({'a': 1, 'b': 2})
sim2 = sim.set('c', 3)  # OK
# sim.set(123, 4)  # Raises CheckedKeyTypeError
# sim.set('d', 'invalid')  # Raises CheckedValueTypeError

# Define a type-checked vector for integers
class IntVector(CheckedPVector):
    __type__ = int

iv = IntVector([1, 2, 3])
iv2 = iv.append(4)  # OK
# iv.append('invalid')  # Raises CheckedValueTypeError

# Define a type-checked set with optional types
class MixedSet(CheckedPSet):
    __type__ = optional(int, str)  # Allows int, str, or None

ms = MixedSet([1, 'hello', None, 2])  # OK
# ms.add(3.14)  # Raises CheckedValueTypeError
```

### Custom Invariants

```python
# Type-checked collections can include custom validation
class PositiveIntVector(CheckedPVector):
    __type__ = int
    
    def __new__(cls, initial=()):
        # Custom validation in constructor
        for item in initial:
            if not isinstance(item, int) or item <= 0:
                raise ValueError("All elements must be positive integers")
        return super().__new__(cls, initial)

piv = PositiveIntVector([1, 2, 3])  # OK
# PositiveIntVector([1, -2, 3])  # Raises ValueError
```

### Serialization

```python
# Type-checked collections can be serialized to regular Python types
class PersonMap(CheckedPMap):
    __key_type__ = str
    __value_type__ = (str, int)

pm = PersonMap({'name': 'Alice', 'age': 30})
regular_dict = pm.serialize()  # Returns {'name': 'Alice', 'age': 30}
print(type(regular_dict))  # <class 'dict'>
```

### Factory Methods

```python
# Use create() class method for explicit construction with validation
data = {'valid_key': 42}
validated_map = StringIntMap.create(data)

# Validation happens during creation
try:
    invalid_data = {123: 'invalid'}
    StringIntMap.create(invalid_data)
except CheckedKeyTypeError as e:
    print(f"Key type error: {e}")
```

### Error Handling

```python
from pyrsistent import CheckedKeyTypeError, CheckedValueTypeError

class TypedData(CheckedPMap):
    __key_type__ = str
    __value_type__ = (int, float)

try:
    td = TypedData()
    td = td.set(42, 'invalid')  # Wrong key type
except CheckedKeyTypeError as e:
    print(f"Expected key types: {e.expected_types}")
    print(f"Actual key type: {e.actual_type}")
    print(f"Actual key value: {e.actual_value}")

try:
    td = TypedData()
    td = td.set('key', 'invalid')  # Wrong value type
except CheckedValueTypeError as e:
    print(f"Expected value types: {e.expected_types}")
    print(f"Actual value type: {e.actual_type}")
    print(f"Actual value: {e.actual_value}")
```