Skip to content

Commit

Permalink
BuiltinRule -> FunctionApplyRule (#1085)
Browse files Browse the repository at this point in the history
The name Builtin is vague, since there are things that are "builtin" (like variables and symbols) that are not functions.
Furthermore, BuiltinRule handles functions added by Mathics3 modules.
  • Loading branch information
rocky authored Sep 17, 2024
1 parent ebdc6b9 commit 6c02434
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 34 deletions.
8 changes: 4 additions & 4 deletions mathics/builtin/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from mathics.core.evaluation import Evaluation
from mathics.core.expression import Expression
from mathics.core.list import ListExpression
from mathics.core.rules import BuiltinRule
from mathics.core.rules import FunctionApplyRule
from mathics.core.symbols import SymbolFalse, SymbolNull, SymbolTrue, strip_context


Expand Down Expand Up @@ -240,19 +240,19 @@ def sort_by_name(tup: tuple):
@staticmethod
def enable_trace(evaluation) -> None:
if TraceBuiltins.traced_definitions is None:
TraceBuiltins.apply_function_copy = BuiltinRule.apply_function
TraceBuiltins.apply_function_copy = FunctionApplyRule.apply_function
TraceBuiltins.definitions_copy = evaluation.definitions

# Replaces apply_function by the custom one
BuiltinRule.apply_function = traced_apply_function
FunctionApplyRule.apply_function = traced_apply_function
# Create new definitions uses the new apply_function
evaluation.definitions = Definitions(add_builtin=True)
else:
evaluation.definitions = TraceBuiltins.definitions_copy

@staticmethod
def disable_trace(evaluation) -> None:
BuiltinRule.apply_function = TraceBuiltins.apply_function_copy
FunctionApplyRule.apply_function = TraceBuiltins.apply_function_copy
evaluation.definitions = TraceBuiltins.definitions_copy

def eval(self, expr, evaluation, options={}):
Expand Down
10 changes: 5 additions & 5 deletions mathics/core/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
from mathics.core.number import PrecisionValueError, dps, get_precision, min_prec
from mathics.core.parser.util import PyMathicsDefinitions, SystemDefinitions
from mathics.core.pattern import Pattern
from mathics.core.rules import BuiltinRule, Rule
from mathics.core.rules import FunctionApplyRule, Rule
from mathics.core.symbols import (
BaseElement,
BooleanType,
Expand Down Expand Up @@ -121,7 +121,7 @@ def eval(x, evaluation):
return Expression(Symbol("G"), x*2)
```
adds a ``BuiltinRule`` to the symbol's definition object that implements
adds a ``FunctionApplyRule`` to the symbol's definition object that implements
``F[x_]->G[x*2]``.
As shown in the example above, leading argument names of the
Expand Down Expand Up @@ -269,11 +269,11 @@ def contribute(self, definitions, is_pymodule=False):
prefix="eval", is_pymodule=is_pymodule
):
rules.append(
BuiltinRule(name, pattern, function, check_options, system=True)
FunctionApplyRule(name, pattern, function, check_options, system=True)
)
for pattern, function in self.get_functions(is_pymodule=is_pymodule):
rules.append(
BuiltinRule(name, pattern, function, check_options, system=True)
FunctionApplyRule(name, pattern, function, check_options, system=True)
)
for pattern_str, replace_str in self.rules.items():
pattern_str = pattern_str % {"name": name}
Expand Down Expand Up @@ -325,7 +325,7 @@ def contextify_form_name(f):
if form not in formatvalues:
formatvalues[form] = []
formatvalues[form].append(
BuiltinRule(name, pattern, function, None, system=True)
FunctionApplyRule(name, pattern, function, None, system=True)
)
for pattern, replace in self.formats.items():
forms, pattern = extract_forms(pattern)
Expand Down
92 changes: 69 additions & 23 deletions mathics/core/rules.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,51 @@
# -*- coding: utf-8 -*-
"""Rules are a core part of the way WMA and Mathics3 executes a
program. Expressions can be transformed by rewrite rules (AKA
transformation rules); builtin functions get matched and applied via a
function signature specified using a BuiltinRule.
"""Rules are a core part of the way Mathematica and Mathics3 execute a
program.
Expressions which are transformed by rewrite rules (AKA transformation
rules) are handed by the `Rule` class.
There are also rules for how to match, assign function parameter
arguments, and then apply a Python "evaluation" function to a Mathics3 Expression.
These kinds of rules are handled by objects in the `FunctionApplyRule` class.
This module contains the classes for these two types of rules.
In a `FunctionApplyRule` rule, the match status of a rule depends on the evaluation return.
For example, suppose that we try to apply rule `F[x_]->x^2` to the expression `F[2]`. The pattern part of the rule,`F[x_]` matches
the expression, `Blank[x]` (or `x_`) is replaced by `2`, giving the substitution expression `2^2`. Evaluation then stops
looking for other rules to be applied over `F[2]`.
On the other hand, suppose that we define a `FunctionApplyRule` that associates `F[x_]` with the function:
```
...
class MyFunction(Builtin):
...
def eval_f(self, x, evaluation) -> Optional[Expression]:
"F[x_]" # pattern part of FunctionApplyRule
if x>3:
return Expression(SymbolPower, x, Integer2)
return None
```
Then, if we apply the rule to `F[2]`, the function is evaluated returning `None`. Then, in the evaluation loop, we get the same
effect as if the pattern didn't match with the expression. The loop continues then with the next rule associated with `F`.
Why do things this way?
Sometimes, the cost of deciding if the rule match is similar to the cost of evaluating the function. Suppose for example a rule
F[x_/;(G[x]>0)]:=G[x]
with G[x] a computationally expensive function. To decide if G[x] is larger than 0, we need to evaluate it,
and once we have evaluated it, just need to return its value.
Also, this allows us to handle several rules in the same function, without relying on our very slow pattern-matching routines.
In particular, this is used for for some critical low-level tasks like building lists in iterators, processing arithmetic expressions,
plotting functions, or evaluating derivatives and integrals.
"""


Expand Down Expand Up @@ -38,16 +78,15 @@ class StopGenerator_BaseRule(StopGenerator):


class BaseRule(KeyComparable, ABC):
"""
This is the base class from which BuiltinRule and Rule classes
are derived from.
"""This is the base class from which the FunctionApplyRule and
Rule classes are derived from.
Rules are part of the rewriting system of Mathics3. See
https://en.wikipedia.org/wiki/Rewriting
This class is not complete in of itself and subclasses should
adapt or fill in what is needed. In particular either ``apply_rule()``
or ``apply_function()`` need to be implemented.
This class is not complete in of itself; subclasses must adapt or
fill in what is needed. In particular either ``apply_rule()`` or
``apply_function()`` need to be implemented.
Note: we want Rules to be serializable so that we can dump and
restore Rules in order to make startup time faster.
Expand Down Expand Up @@ -89,7 +128,7 @@ def yield_match(vars, rest):
del vars[name]
apply_fn = (
self.apply_function
if isinstance(self, BuiltinRule)
if isinstance(self, FunctionApplyRule)
else self.apply_rule
)
new_expression = apply_fn(expression, vars, options, evaluation)
Expand Down Expand Up @@ -137,10 +176,14 @@ def yield_match(vars, rest):
else:
return None

def apply_rule(self):
def apply_rule(
self, expression: BaseElement, vars: dict, options: dict, evaluation: Evaluation
):
raise NotImplementedError

def apply_function(self):
def apply_function(
self, expression: BaseElement, vars: dict, options: dict, evaluation: Evaluation
):
raise NotImplementedError

def get_sort_key(self) -> tuple:
Expand Down Expand Up @@ -216,11 +259,11 @@ def __repr__(self) -> str:
return "<Rule: %s -> %s>" % (self.pattern, self.replace)


# FIXME: the class name would be better called FunctionCallRule.
class BuiltinRule(BaseRule):
class FunctionApplyRule(BaseRule):
"""
A BuiltinRule is a rule that has a replacement term that is associated
a Python function rather than a Mathics Expression as happens in a Rule.
A FunctionApplyRule is a rule that has a replacement term that
is associated a Python function rather than a Mathics Expression
as happens in a transformation Rule.
Each time the Pattern part of the Rule matches an Expression, the
matching subexpression is replaced by the expression returned
Expand All @@ -229,7 +272,7 @@ class BuiltinRule(BaseRule):
Parameters for the function are bound to parameters matched by the pattern.
Here is an example taken from the symbol ``System`Plus``.
It has has associated a BuiltinRule::
It has has associated a FunctionApplyRule::
Plus[items___] -> mathics.builtin.arithfns.basic.Plus.apply
Expand All @@ -242,8 +285,8 @@ class BuiltinRule(BaseRule):
The return value of this function is ``Times[2, a]`` (or more compactly: ``2*a``).
When replaced in the original expression, the result is: ``F[2*a]``.
In contrast to Rule, BuiltinRules can change the state of definitions
in the the system.
In contrast to (transformation) Rules, FunctionApplyRules can
change the state of definitions in the the system.
For example, the rule::
Expand All @@ -254,8 +297,9 @@ class BuiltinRule(BaseRule):
sets the attribute ``NumericFunction`` in the definition of the symbol ``F`` and
returns Null (``SymbolNull`)`.
This will cause `Expression.evalate() to perform an additional
This will cause `Expression.evaluate() to perform an additional
``rewrite_apply_eval()`` step.
"""

def __init__(
Expand All @@ -267,7 +311,9 @@ def __init__(
system: bool = False,
evaluation: Optional[Evaluation] = None,
) -> None:
super(BuiltinRule, self).__init__(pattern, system=system, evaluation=evaluation)
super(FunctionApplyRule, self).__init__(
pattern, system=system, evaluation=evaluation
)
self.name = name
self.function = function
self.check_options = check_options
Expand All @@ -293,7 +339,7 @@ def apply_function(
return self.function(evaluation=evaluation, **vars_noctx)

def __repr__(self) -> str:
return "<BuiltinRule: %s -> %s>" % (self.pattern, self.function)
return "<FunctionApplyRule: %s -> %s>" % (self.pattern, self.function)

def __getstate__(self):
odict = self.__dict__.copy()
Expand Down
4 changes: 2 additions & 2 deletions mathics/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from mathics.core.load_builtin import import_and_load_builtins
from mathics.core.parser import MathicsFileLineFeeder, MathicsLineFeeder
from mathics.core.read import channel_to_stream
from mathics.core.rules import BuiltinRule
from mathics.core.rules import FunctionApplyRule
from mathics.core.streams import stream_manager
from mathics.core.symbols import SymbolNull, strip_context
from mathics.eval.files_io.files import set_input_var
Expand Down Expand Up @@ -385,7 +385,7 @@ def main() -> int:
extension_modules = default_pymathics_modules

if args.trace_builtins:
BuiltinRule.apply_rule = traced_apply_function
FunctionApplyRule.apply_rule = traced_apply_function

def dump_tracing_stats():
TraceBuiltins.dump_tracing_stats(sort_by="count", evaluation=None)
Expand Down

0 comments on commit 6c02434

Please sign in to comment.