Skip to content

Support for constrained decoding with dependent types #441

@kiranandcode

Description

@kiranandcode

Opening an issue to discuss this language design feature.

I guess this kind of dynamic class creation is allowed, but it's not very Pythonic. If this kind of dependent type checking is something we want to support/encourage, we should think about how to make it easier and more idiomatic to express.

Originally posted by @eb8680 in #404 (comment)

Pydantic models support field validation which allows placing additional constraints on instances of a type.

Often, the model validation logic may want to use runtime values from elsewhere in the program - for example:

# hypothetical syntax - not supported in python
class ValidStep[no_towers: int, valid_moves: set[tuple[int,int]]]:
   start: int
   end: int
   @pydantic.field_validator("start","end")
   def _validate_field(cls, v, info): 
        assert 0 <= v < no_towers
        return v
   @pydantic.model_validator("after")
   def _validate_model(self):
      assert (self.start, self.end) in valid_moves
      return self

The above captures a step that is valid with respect to some game board. The syntax above isn't supported by pydantic, so in practice to implement the above, you'd need to do something like:

def build_validated_model(game_state: GameState) -> type[Step]:
    valid_steps = game_state.valid_steps()

    @pydantic.dataclasses.dataclass(frozen=True)
    class StepModel(Step):
        start: int
        end: int
        explanation: str = ""
        model_config = ConfigDict(extra="forbid")

        @pydantic.field_validator("start", "end", mode="before")
        def validate_indices(cls, v, info):
            if isinstance(v, int):
                if not (0 <= v < len(game_state.towers)):
                    raise ValueError(f"{info.field_name} {v} out of range")
            else:
                raise TypeError("start/end must both be int")
            return v

        @pydantic.model_validator(mode="after")
        def validate_step(self):
            if (self.start, self.end) not in valid_steps:
                raise ValueError("step is not in {self.valid_steps}")
            return self

        def __hash__(self):
            return hash((self.start, self.end))

    return StepModel

Metadata

Metadata

Assignees

No one assigned
    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions