Tagged unions
pane
supports parsing tagged unions, which are invaluable
in representing complex data types. Unlike a untagged union (represented by t.Union
),
tagged unions use a discriminating value to separate variants unambiguously.
Tagged unions are specified with the Tagged
annotation wrapping a t.Union
:
import pane
from pane.annotations import Tagged
class Variant1(pane.PaneBase):
x: t.Literal['variant1'] = 'variant1'
y: int = 6
class Variant2(pane.PaneBase):
x: t.Literal['variant2'] = 'variant2'
y: str = 'mystring'
class Variant3(pane.PaneBase):
x: t.Literal['variant3'] = 'variant3'
y: int = 6
z: int = 7
TaggedUnion = t.Annotated[t.Union[Variant1, Variant2, Variant3], Tagged('x')]
This specifies a tagged union with a tag (in Python) of 'x'
. Attribute 'x'
is examined for each variant type, so that every possible value is uniquely associated
with a type.
When converting a value, the tag is matched first, and then the variant corresponding to that tag:
>>> pane.convert({'x': 'variant3'}, TaggedUnion)
Variant3(x='variant3', y=6, z=7)
>>> pane.convert({'x': 'variant2', 'y': 'str'}, TaggedUnion)
Variant2(x='variant2', y='str')
>>> pane.convert({'x': 'unknown'}, TaggedUnion)
Traceback (most recent call last):
...
pane.errors.ConvertError: Expected tag 'x' one of 'variant1', 'variant2', or 'variant3', instead got `unknown` of type `str`
Note that if we had used an untagged union instead, we would have no way to distinguish
between Variant1
and Variant3
in general.
Tagged Union Layouts
By default, tagged unions are stored in the 'internally tagged' format, where tags are stored alongside the variant's values.
Two other layouts are possible. First, the 'externally tagged' layout:
>>> ExtTagged = t.Annotated[t.Union[Variant1, Variant2], Tagged('x', external=True)]
>>> pane.convert({'variant2': {'y': 'str'}}, ExtTagged)
Variant2(x='variant2', y='str')
In this format, the tag is stored as the sole key in a mapping enclosing the variant object. The externally tagged format is often used by functional, type-safe languages such as Rust.
The final layout is the 'adjacently tagged' layout:
>>> AdjTagged = t.Annotated[t.Union[Variant1, Variant2], Tagged('x', external=('t', 'c'))]
>>> pane.convert({'t': 'variant1', 'c': {'y': 8}}, AdjTagged)
Variant1(x='variant1', y=8)
In this format, the tag and content are stored alongside each other in a mapping. The
tuple ('t', 'c')
specifies the keys identifying the tag and content respectively. This
format is often used in Haskell.
These tagged union layouts (along with untagged unions) are modeled after the
enum representations in Rust's serde
library.