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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions