Interfacing with SymbolicUtils.jl

This section is for Julia package developers who may want to use the simplify and rule rewriting system on their own expression types.

Defining the interface

SymbolicUtils matchers can match any Julia object that implements an interface to traverse it as a tree.

In particular, the following methods should be defined for an expression tree type T with symbol types S to work with SymbolicUtils.jl


Check if x represents an expression tree. If returns true, it will be assumed that operation(::T) and arguments(::T) methods are defined. Definining these three should allow use of simplify on custom types. Optionally symtype(x) can be defined to return the expected type of the symbolic expression.


Returns the operation (a function object) performed by an expression tree. Called only if istree(::T) is true. Part of the API required for simplify to work. Other required methods are arguments and istree


Returns the arguments (a Vector) for an expression tree. Called only if istree(x) is true. Part of the API required for simplify to work. Other required methods are operation and istree

In addition, the methods for Base.hash and Base.isequal should also be implemented by the types for the purposes of substitution and equality matching respectively.

similarterm(t::MyType, f, args[, T])

Construct a new term with the operation f and arguments args, the term should be similar to t in type. if t is a Term object a new Term is created with the same symtype as t. If not, the result is computed as f(args...). Defining this method for your term type will reduce any performance loss in performing f(args...) (esp. the splatting, and redundant type computation). T is the symtype of the output term. You can use promote_symtype to infer this type.

The below two functions are internal to SymbolicUtils



The supposed type of values in the domain of x. Tracing tools can use this type to pick the right method to run or analyse code.

This defaults to typeof(x) if x is numeric, or Any otherwise. For the types defined in this package, namely T<:Symbolic{S} it is S.

Define this for your symbolic types if you want simplify to apply rules specific to numbers (such as commutativity of multiplication). Or such rules that may be implemented in the future.

promote_symtype(f, arg_symtypes...)

Returns the appropriate output type of applying f on arguments of type arg_symtypes.


Suppose you were feeling the temptations of type piracy and wanted to make a quick and dirty symbolic library built on top of Julia's Expr type, e.g.

for f ∈ [:+, :-, :*, :/, :^] #Note, this is type piracy!
    @eval begin
        Base.$f(x::Union{Expr, Symbol}, y::Number) = Expr(:call, $f, x, y)
        Base.$f(x::Number, y::Union{Expr, Symbol}) = Expr(:call, $f, x, y)
        Base.$f(x::Union{Expr, Symbol}, y::Union{Expr, Symbol}) = (Expr(:call, $f, x, y))

ex = 1 + (:x - 2)
:((+)(1, (-)(x, 2)))

How can we use SymbolicUtils.jl to convert ex to (-)(:x, 1)? We simply implement istree, operation, arguments and we'll be able to do rule-based rewriting on Exprs:

using SymbolicUtils

SymbolicUtils.istree(ex::Expr) = ex.head == :call
SymbolicUtils.operation(ex::Expr) = ex.args[1]
SymbolicUtils.arguments(ex::Expr) = ex.args[2:end]

@rule(~x => ~x - 1)(ex)
:((-)((+)(1, (-)(x, 2)), 1))

However, this is not enough to get SymbolicUtils to use its own algebraic simplification system on Exprs:

:((+)(1, (-)(x, 2)))

The reason that the expression was not simplified is that the expression tree is untyped, so SymbolicUtils doesn't know what rules to apply to the expression. To mimic the behaviour of most computer algebra systems, the simplest thing to do would be to assume that all Exprs are of type Number:

SymbolicUtils.symtype(s::Expr) = Number

:((+)(-1, x))

Now SymbolicUtils is able to apply the Number simplification rule to Expr.