@@ -335,7 +335,7 @@ We'll consider postorder traversal first, as it's the easier to implement.
335
335
fn: function(node, *fn_children)
336
336
A function to be applied at each node. The function should take the
337
337
node to be visited as its first argument, and the results of visiting
338
- its parent as any further arguments.
338
+ its children as any further arguments.
339
339
'''
340
340
341
341
return fn(tree, *(postvisitor(c, fn) for c in tree.children))
@@ -543,7 +543,9 @@ will that comprise?
543
543
right thing to do is to turn the number into an expression by instantiating a
544
544
:class: `Number ` with it as a value. Once this has been done, the number is
545
545
just another :class: `Expression ` obeying the same arithmetic rules as other
546
- expressions.
546
+ expressions. The need to accommodate operations between symbolic expressions
547
+ and numbers also implies that it will also be necessary to implement the
548
+ :term: `special functions ` for reversed arithmetic operations.
547
549
548
550
Let's now consider :class: `Operator `. The operations for creating string
549
551
representations can be implemented here, because they will be the same for all
@@ -649,12 +651,12 @@ code to execute, depending on the type of the first argument [#single]_.
649
651
650
652
@singledispatch
651
653
def evaluate(expr, *o, **kwargs):
652
- """Evaluate an expression.
654
+ """Evaluate an expression node .
653
655
654
656
Parameters
655
657
----------
656
658
expr: Expression
657
- The expression to be evaluated.
659
+ The expression node to be evaluated.
658
660
*o: numbers.Number
659
661
The results of evaluating the operands of expr.
660
662
**kwargs:
@@ -704,26 +706,158 @@ code to execute, depending on the type of the first argument [#single]_.
704
706
705
707
:numref: `tree_evaluate ` shows a single dispatch function for a visitor function
706
708
which evaluates a :class: `Expression `. Start with lines 6-19. These define a
707
- function :func: `~example_code/ expression_tools/ evaluate ` which will be used in
709
+ function :func: `~example_code. expression_tools. evaluate ` which will be used in
708
710
the default case, that is, in the case where the :class: `type ` of the first
709
711
argument doesn't match any of the other implementations of
710
- :func: `~example_code/ expression_tools/ evaluate `. In this case, the first
712
+ :func: `~example_code. expression_tools. evaluate `. In this case, the first
711
713
argument is the expression that we're evaluating, so if the type doesn't match
712
714
then this means that we don't know how to evaluate this object, and the only
713
715
course of action available is to throw an :term: `exception `.
714
716
715
- The new feature that we haven't met before occurs on line 5.
717
+ The new feature that we haven't met before appears on line 5.
716
718
:func: `functools.singledispatch ` turns a function into
717
719
a single dispatch function. The `@ ` symbol marks
718
720
:func: `~functools.singledispatch ` as a :term: `decorator `. We'll return to them
719
721
in :numref: `decorators `. For the moment, we just need to know that
720
722
`@singledispatch ` turns the function it precedes into a single dispatch
721
723
function.
722
724
725
+ Next we turn our attention to the implementation of evaluation for the different
726
+ expression types. Look first at lines 26-28, which provide the evaluation of
727
+ :class: `Number ` nodes. The function body is trivial: the evaluation of a
728
+ :class: `Number ` is simply its value. The function interface is more interesting.
729
+ Notice that the function name is given as `_ `. This is the Python convention for
730
+ a name which will never be used. This function will never be called by its
731
+ declared name. Instead, look at the decorator on line 26. The single dispatch
732
+ function :func: `~example_code.expression_tools.evaluate ` has a :term: `method `
733
+ :meth: `register `. When used as a decorator, the :meth: `register ` method of a
734
+ single dispatch function registers the function that follows as implementation
735
+ for the :keyword: `class ` given as an argument to :meth: `register `. On this
736
+ occasion, this is :class: `expressions.Number `.
737
+
738
+ Now look at lines 31-33. These contain the implementation of
739
+ :func: `~example_code.expression_tools.evaluate ` for :class: `expressions.Symbol `.
740
+ In order to evaluate a symbol, we depend on the mapping from symbol names to
741
+ numerical values that has been passed in.
742
+
743
+ Finally, look at lines 36-38. These define the evaluation visitor for addition.
744
+ This works simply by adding the results of evaluating the two operands of
745
+ :class: `expressions.Add `. The evaluation visitors for the other operators follow
746
+ *mutatis mutandis *.
747
+
748
+ An expanded tree visitor
749
+ ~~~~~~~~~~~~~~~~~~~~~~~~
750
+
751
+ The need to provide the `symbol_map ` parameter to the
752
+ :class: `expressions.Symbol ` evaluation visitor means that the postorder visitor
753
+ in :numref: `postorder_recursive ` is not quite up to the task.
754
+ :numref: `postorder_recursive_kwargs ` extends the tree visitor to pass arbitrary
755
+ keyword arguments through to the visitor function.
756
+
757
+ .. _postorder_recursive_kwargs :
723
758
759
+ .. code-block :: python3
760
+ :caption: A recursive tree visitor that passes any keyword arguments
761
+ through to the visitor function. We also account for the name changes
762
+ between :class:`~example_code.graphs.TreeNode` and :class:`Expression`.
763
+ :linenos:
764
+
765
+ def postvisitor(expr, fn, **kwargs):
766
+ '''Traverse an Expression in postorder applying a function to every node.
767
+
768
+ Parameters
769
+ ----------
770
+ expr: Expression
771
+ The expression to be visited.
772
+ fn: function(node, *o, **kwargs)
773
+ A function to be applied at each node. The function should take the
774
+ node to be visited as its first argument, and the results of visiting
775
+ its operands as any further positional arguments. Any additional
776
+ information that the visitor requires can be passed in as keyword
777
+ arguments.
778
+ **kwargs:
779
+ Any additional keyword arguments to be passed to fn.
780
+ '''
781
+
782
+ return fn(expr,
783
+ *(postvisitor(c, fn, **kwargs) for c in expr.operands),
784
+ **kwargs)
785
+
786
+
787
+ Assuming we have an implementation of our simple expression language, we are now
788
+ in a position to try out our expression evaluator:
789
+
790
+ .. code-block :: ipython3
791
+
792
+ In [1]: from expressions import Symbol
724
793
725
- Expressions as :term: `DAGs <DAG> `
726
- ---------------------------------
794
+ In [2]: from example_code.expression_tools import evaluate, postvisitor
795
+
796
+ In [3]: x = Symbol('x')
797
+
798
+ In [4]: y = Symbol('y')
799
+
800
+ In [5]: expr = 3*x + 2**(y/5) - 1
801
+
802
+ In [6]: print(expr)
803
+ 3 ⨉ x + 2 ^ (y / 5) - 1
804
+
805
+ In [7]: postvisitor(expr, evaluate, symbol_map={'x': 1.5, 'y': 10})
806
+ Out[7]: 7.5
807
+
808
+
809
+ :term: `DAGs <DAG> ` and non-recursive tree visitors
810
+ --------------------------------------------------
811
+
812
+ If we treat an expression as a tree, then any repeated subexpressions will be
813
+ duplicated in the tree. Consider, for example, :math: `x^2 + 3 /x^2 `. If we create
814
+ a tree of this expression, then :math: `x^2 ` will occur twice, and any operation
815
+ that we perform on :math: `x^2 ` will have to be done twice. If, on the other hand, we
816
+ treat the expression as a more general :term: `directed acyclic graph `, then the
817
+ single subexpression :math: `x^2 ` can have multiple parents, and so can appear as
818
+ an operand more than once. :numref: `tree_vs_dag ` illustrates this situation.
819
+
820
+ .. _tree_vs_dag :
821
+
822
+ .. graphviz ::
823
+ :caption: :term: `Tree <tree> ` (left) and :term: `DAG ` (right) representations
824
+ of the expression :math: `x^2 + 3 /x^2 `. Notice that the :term: `DAG `
825
+ representation avoids the duplication of the :math: `x^2 ` term.
826
+
827
+ strict digraph{
828
+ a1 [label="+"]
829
+ pow1 [label="^"]
830
+ x1 [label="x"]
831
+ n1 [label="2"]
832
+ a1 -> pow1
833
+ pow1 -> x1
834
+ pow1 -> n1
835
+ m1 [label="/"]
836
+ n0 [label=3]
837
+ pow2 [label="^"]
838
+ x2 [label="x"]
839
+ n2 [label="2"]
840
+ a1 -> m1
841
+ m1 -> n0
842
+ m1 -> pow2
843
+ pow2 -> x2
844
+ pow2 -> n2
845
+
846
+ a3 [label="+", ordering="out"]
847
+ pow3 [label="^"]
848
+ x3 [label="x"]
849
+ n3 [label="2"]
850
+ a3 -> pow3
851
+
852
+ pow3 -> x3
853
+ pow3 -> n3
854
+ m3 [label="/", ordering="out"]
855
+ n4 [label=3]
856
+ a3 -> m3
857
+ m3 -> n4
858
+ m3 -> pow3
859
+
860
+ }
727
861
728
862
Glossary
729
863
--------
0 commit comments