Enough metaprogramming for dataclass
In SPy roadmap, there is the TODO item “enough metaprogramming capabilities to implement e.g. dataclasses”.
Here is a reasonable minimal dataclass implementation in future SPy:
from operator import OpSpec
from __spy__ import unroll_range
@blue.generic
def dataclass(cls):
@blue
def get_fields():
# needs: read cls's annotated body as an ordered {name: type} dict,
# the way @struct itself already must do internally for a normal
# `field: T` class body. Not yet exposed to user-level blue code
# in any of the examples above.
return cls.__annotations__
fields = get_fields()
names = (*fields.keys(),)
types = (*fields.values(),)
N = len(names)
@struct
class _dc:
__extra_fields__ = fields
@blue.metafunc
def __new__(m_self, *args_m):
arg_types = args_m.types
# could raise TypeError here if arg_types != types
def new(*args: *arg_types) -> _dc:
return _dc.__make__(*args)
return OpSpec(impl, [*args_m])
@blue.metafunc
def __eq__(m_self, m_other):
def impl(self: _dc, other: _dc) -> bool:
ok = True
for i in unroll_range(N):
if getattr(self, names[i]) != getattr(other, names[i]):
ok = False
return ok
return OpSpec(impl, [m_self, m_other])
@blue.metafunc
def __repr__(m_self):
def impl(self: _dc) -> str:
s = "Point("
for i in unroll_range(N):
if i > 0:
s = s + ", "
s = s + names[i] + "=" + repr(getattr(self, names[i]))
return s + ")"
return OpSpec(impl, [m_self])
return _dc
@dataclass
class Point:
x: f64
y: f64List of features to be implemented¶
Different things are currently not implemented.
unroll for loops¶
This is the most complicated feature and Antonio is working on it (September-October 2026).
cls.__annotations__ (or equivalent)¶
This should be a small addition.
fields.key() and fields.values()¶
Note that fields is a dict[str, type], which is implemented as a
interp_dict[str, type].
But “method __spy__::interp_dict[str, type]::keys does not exist”.
(*fields.keys(),) and OpSpec(impl, [*args_m]) in blue functions¶
Being implemented in spylang/spy#710
*args: *args_m.types for red functions arguments¶
args would be a tuple[*args_m.types] (PEP 646).
unroll_range as a splice generator¶
This is a direct follow up of unroll for loops.
*(args[i] for i in unroll_range(len(args))) could be redshifted as
args[0], args[1], ...
Once we have unroll for loops, the implementation should not be very difficult.
Call with starred (func(*args)) in red functions¶
func(*args) could then be syntactic sugar for
func(*(args[i] for i in unroll_range(len(args)))).