From d3e99ab90a47397d9822df58772ff3e2ac5e8a97 Mon Sep 17 00:00:00 2001 From: rocky Date: Mon, 16 Sep 2024 10:28:35 -0400 Subject: [PATCH] Add mmatera's information on Rules and application --- mathics/core/rules.py | 51 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/mathics/core/rules.py b/mathics/core/rules.py index 4285b72c6..6af8f5ff5 100644 --- a/mathics/core/rules.py +++ b/mathics/core/rules.py @@ -1,15 +1,51 @@ # -*- coding: utf-8 -*- -"""Rules are a core part of the way WMA and Mathics3 executes a +"""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. +rules) are handed by the `Rule` class. -There are also rules for how to match, assign parameter arguments, and -apply Mathics3 functions that are implemented in Python code. The -FunctionApplyRule class handles this. +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. + """ @@ -224,7 +260,8 @@ def __repr__(self) -> str: class FunctionApplyRule(BaseRule): - """A FunctionApplyRule is a rule that has a replacement term that + """ + 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. @@ -260,7 +297,7 @@ class FunctionApplyRule(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. """