An alternative to Annotated

I think that the : is really jarring because we look for the parameter name to the left of a colon.

Among all overloadable operators, | doesn’t actually have the lowest precedence–comparison operators do.

So how about repurposing the < operator instead?

def create_user(name: str | None < Doc("The User's name")): ...

Reads intuitively enough to me like an arrow pointing additional information to what’s on the left.

1 Like

This feels weird because subtype relations can be expressed via such relation operators. But I do prefer in abstract to the precedence issues. It also has the additional difficulty that it doesn’t chain because of the special behavior for comparison operators.

1 Like

I’m not sure what you’re referring to because currently subtype relations can’t be expressed by comparison operators. Is there a PEP draft or a discussion somewhere that proposes to overload comparison operators for such a purpose?

That’s true in theory, but I can’t quite imagine a meaningful real-world scenario where comparison chaining would be used in a type annotation for this to be a real issue.

class Box(Struct):
    x: int < SInt(16) < Doc("center of the box")
    ...

Would be executed as

class Box(Struct:
    x: (int < (_t:=SInt(16)) and (_t < Doc("center of the box"))

Which obviously would cause mayhem. And this is just a part of python syntax, we can’t work around that.

I guess we could use a different operator to compose different Annotations together and < acts a separator between the two worlds.

(to a slightly lesser extend I don’t like that < isn’t symmetric neither in symbol nor common meaning - but can get over that)

Not in python, and apparently not in the python typing docs. But in the wider literature this isn’t absurd and follows e.g. from the subset relations python type systems is somewhat based. And python does use < for issubset test.

It could be confusing for readers who are familiar with that area of CS/math.

1 Like

I see. Yeah I was indeed thinking of using | instead to compose different Annotations together.

Yeah but I think it would be clear from the context of : that < is meant to be part of a type annotation rather than some sort of a Boolean evaluation of a subtype relation. It isn’t like we’re going to see it in a statement like if int < Doc("center of the box"):.

By the way, @ can also work even if it has a higher precedence than |. The object returned by __matmul__ just needs to be able to support | itself to keep track of the types being Union’d while propagating the annotation.

1 Like

Yeah, the object returned by the __matmul__ method of the base class of all types will implement __ror__ to support Union of types on the left, and __or__ to support composition of multiple annotations on the right, so the example above can work even though it’s evaluated as:

str | (None @ MaxLen(32)) | Unique

I made a small concrete example of the runtime semantics I am imagining, using @ and |: https://github.com/MegaIng/xannotationlib

I am open to PR for additional (simple) examples or suggestions to improve wording. I attempted to write down a semi-formal spec, but I am not really happy with what I came up with. I would especially like input on the behavior description for type checkers.

1 Like

The repo doesn’t seem to be public (404).

2 Likes

I think @ is intuitive because we can think of “Annotations” as type decorations. Something adjunct/crosscutting to modify a type.

I like @ more than Annotated, but maybe we also need a way to indicate that annotations are not being used for typing in a file? The problem with Annotated and @ are that both still require expressing a type to use (even if that type is just Any to opt out), while other uses of annotations are perfectly reasonable.

No, this is explicitly not needed, although I guess I didn’t provide an example of that.

class Box(CStruct):
    x: SInt(16)
    y: SInt(16)
    w: UInt(16)
    h: UInt(16)

is also valid. By virtue of being BaseAnnotation instances type checkers can easily decide that these are not supposed to be type hints. They should then treat the symbol as-if there is no annotation there, whatever that means for the type checker.

The fact that the above annotations still have an effect on the signature of Box is something that needs to be communicated via a plugin/future standard anyway.

2 Likes

PEP 835: Shorthand syntax for Annotated type metadata is a PEP proposing a similar solution.

2 Likes

The two central behavioral differences are:

  • my proposal “fixes” the precendence by automatically propgating annotations outwards so that int | None @ ann == (int | None) @ ann == int | (None @ ann), which the PEP explictly decided to not support
  • The a core point of my proposal was for annotations to be on their own without any type hint (implicitly resulting in Any for the purpose of type checking)

But I am not too attached to either of these.