when making a datatype sort from a class, I couldn't find a way to programmatically create methods to get turned into constructors. My motivating use case is for integrating with a compiler where the built in types and operators are internally extensible defined with a registry+interface system. I figure I could just fall back to string or int labels with (op-wrapper op_name args) pattern but my (unsourced, entirely vibe based) guess would be that would be worse for performance? i'm coming from egg though so I don't have a strong intuition for query performance influences in egglog quite yet.
i have a hacked together solution that seems to work fine, but a neater solution probably exists. It can't just use a class decorator or otherwise just add the method via setattr since that doesn't seem to add it to the namespace dict that the expr metaclass looks through for methods.
@dataclass(frozen=True)
class DynamicMethodInfo:
name: str
# all types provided in annotations need to be Expr-convertable still.
args_anns: list[tuple[str, str]] # (arg_name, arg_type) <- arg_type should be exactly as written in annotation. don't str(cls) it!
# defaults to the type of the class implementing it.
return_type: Optional[str] = None
is_class_method: bool = True
# args here should match the annotated args. return type should match up too. include cls up front if writing a classmethod
default_impl: Optional[callable] = None
def DynamicExprMeta(method_provider: list[DynamicMethodInfo]) -> type:
class _AddMethodsMeta(_ExprMetaclass):
"""Metaclass for dynamically adding methods to an egglog datatype class"""
def __new__(
cls: type[_AddMethodsMeta],
name: str,
bases: tuple[type, ...],
namespace: dict[str, Any],
egg_sort: str | None = None,
ruleset: Ruleset | None = None,
):
# print(method_provider)
for mthd_info in method_provider:
new_method = mthd_info.default_impl
if new_method is None:
lambda_args = [arg for (arg, _) in mthd_info.args_anns]
if mthd_info.is_class_method:
lambda_args.insert(0, "cls")
new_method = eval(f'lambda {", ".join(lambda_args)}: None')
if mthd_info.is_class_method:
new_method = classmethod(new_method)
for (arg_lbl, arg_type) in mthd_info.args_anns:
new_method.__annotations__[arg_lbl] = arg_type
new_method.__annotations__["return"] = mthd_info.return_type or name
setattr(cls, mthd_info.name, new_method)
# print(new_method.__annotations__)
namespace[mthd_info.name] = new_method
# print(namespace)
return super().__new__(cls, name, bases, namespace, egg_sort, ruleset)
return _AddMethodsMeta
class TestDynamicExpr(Expr, metaclass=DynamicExprMeta([DynamicMethodInfo("test", [("arg1", "i64Like")])])):
pass
when making a datatype sort from a class, I couldn't find a way to programmatically create methods to get turned into constructors. My motivating use case is for integrating with a compiler where the built in types and operators are internally extensible defined with a registry+interface system. I figure I could just fall back to string or int labels with (op-wrapper op_name args) pattern but my (unsourced, entirely vibe based) guess would be that would be worse for performance? i'm coming from egg though so I don't have a strong intuition for query performance influences in egglog quite yet.
i have a hacked together solution that seems to work fine, but a neater solution probably exists. It can't just use a class decorator or otherwise just add the method via setattr since that doesn't seem to add it to the namespace dict that the expr metaclass looks through for methods.