Consider the following Abstract Data Type (using Haskell syntax):
data Expr = Literal String | Symbol String | And [Expr] | Or [Expr]
In Python, one can make use of dataclasses and inheritance to obtain a similar type construction:
@dataclass
class Expr:
# "union"
foo: str | None = None
bar: list["Expr"] | None = None
@dataclass
class Literal(Expr):
pass
@dataclass
class Symbol(Expr):
pass
@dataclass
class And(Expr):
pass
@dataclass
class Or(Expr):
pass
As an interesting exercise, I am wondering whether it is possible to obtain a similar effect, but with a different definition which avoids duplication. I came up with the following theoretical notation:
# simulate Haskell's
# data Expr = Literal String | Symbol String | And [Expr] | Or [Expr]
# the following will bring names into scope
(
make_adt(
type_cons="Expr",
possible_fields=[
("foo", str),
("bar", list["Expr"]),
],
)
.add_data_cons("Literal", fields=["foo"])
.add_data_cons("Symbol", fields=["foo"])
.add_data_cons("And", fields=["bar"])
.add_data_cons("Or", fields=["bar"])
)
Here I am saying that there's a base type (the Expr type constructor) with 4 data constructors: Literal, Symbol, And, Or.
Each data constructor takes an additional argument (either str or list[Expr]), which is referred in the fields argument above (must be a subset of the possible_fields).
So:
Literal("foo"): sets thefoofield for the instanceAnd([Literal("foo"), Symbol("baz")]): sets thebarfield for the instance
The constraint here, as opposed to plain inheritance, is that that Literal and Symbol don't have the bar field, and similarly, And, Or, don't have the foo field. Or, to relax this a bit, we at least have to enforce that only non-null attributes are the ones defined in the fields list above.
My questions are:
- Can something like this be implemented?
- I'm thinking along the lines of attrs and dynamic
classcreation usingtype(...).
- I'm thinking along the lines of attrs and dynamic
- How brittle it would be?
P.S. I know it does not necessarily make sense to over-engineer this, especially in a dynamically typed language like Python, but I consider it to be an interesting exercise nonetheless.
type, like:MyClass = type("MyClass", (object,), {}), then instantiate objects:foo = MyClass(). From the docs: With three arguments, return a new type object. This is essentially a dynamic form of the class statement.classstatement in a function and return that class for simple cases.eval()orexec()most cases, seenamedtuple()for example.ast.unparse, which means you can use whatever more expressive syntax you'd like and use Python'sastlibrary to generate Python modules proper.