Custom Operators

This tutorial will demonstrate how to implement custom operators using the operator Interface. This interface can be used to implement operators with custom behavior such as:

  • Number of parameters
  • Lazy and eager evaluation
  • Semantics
  • Associativity
  • Initial value
  • Pretty printing
  • Side-effects
Warning

This tutorial is not yet polished. This interface is incomplete and will be changed in v0.4.

Setup

Implementing an operator requires defining methods for that operator. To do so, their function names must be imported or prefixed by the Interface module. This module also exports several other required and useful functions.

julia> import PAndQ:
           Associativity, Evaluation, dual, evaluate,
           initial_value, parenthesize, print_expression, symbolWARNING: could not import PAndQ.Evaluation into Main
julia> using PAndQ, .Interface

Nullary

This is a renamed tautology operator. First, define an Operator. If possible, this should be a constant whose name corresponds to the operator name.

julia> const truth = Operator{:truth}()
Error showing value of type Operator{:truth}:
ERROR: InterfaceError: implement `symbol` for `Operator{:truth}()`

If a required method is not implemented, a runtime error will display the function and operator that a method must be implemented for. The error says to implement symbol. This function is used to print an operator.

julia> symbol(::typeof(truth)) = "truth";ERROR: UndefVarError: `truth` not defined
julia> truthERROR: UndefVarError: `truth` not defined
julia> truth()ERROR: UndefVarError: `truth` not defined

The error says to implement Evaluation. This function is used to specify whether an operator lazily or eagerly evaluates its arguments.

julia> Evaluation(::typeof(truth)) = Lazy;

julia> truth()
Error showing value of type PAndQ.Tree{0}:
ERROR: InterfaceError: implement `print_expression` for `Operator{:truth}()` with `0` propositions

The error says to implement print_expression. This function is used to print a node of a syntax tree.

julia> print_expression(io, o::typeof(truth), ps) = show(io, "text/plain", o);ERROR: UndefVarError: `truth` not defined
julia> truth()ERROR: UndefVarError: `truth` not defined
julia> print_table(truth())ERROR: UndefVarError: `truth` not defined

The error says to implement evaluate. This function is used to specify the semantics of an operator.

julia> evaluate(::typeof(truth), ps) = ⊤;ERROR: UndefVarError: `truth` not defined
julia> print_table(truth())ERROR: UndefVarError: `truth` not defined

Unary

This is an eagerly evaluated not operator.

julia> const negate = Operator{:negate}();ERROR: TypeError: in Type{...} expression, expected UnionAll, got Type{PAndQ.Interface.Operator}
julia> symbol(::typeof(negate)) = "negate";ERROR: UndefVarError: `negate` not defined
julia> negateERROR: UndefVarError: `negate` not defined
julia> Evaluation(::typeof(negate)) = Eager;ERROR: UndefVarError: `negate` not defined
julia> evaluate(::typeof(negate), ps) = evaluate(¬, ps);ERROR: UndefVarError: `negate` not defined
julia> @atomize negate(¬p)ERROR: MethodError: objects of type PAndQ.AbstractSyntaxTree are not callable
julia> @atomize print_table(negate(p))ERROR: MethodError: objects of type PAndQ.AbstractSyntaxTree are not callable

Binary

This is an imply operator represented by the --> symbol.

julia> const if_then = --> = Operator{:if_then}();ERROR: TypeError: in Type{...} expression, expected UnionAll, got Type{PAndQ.Interface.Operator}
julia> symbol(::typeof(-->)) = "-->";ERROR: UndefVarError: `-->` not defined
julia> -->ERROR: UndefVarError: `-->` not defined
julia> Evaluation(::typeof(-->)) = Lazy;ERROR: UndefVarError: `-->` not defined

If a node in a syntax tree is not the root node, it may be necessary to parenthesize it to avoid ambiguity. The parenthesize function is used to print parentheses around a node if it is not the root node. The print_proposition function is used to print the propositions in a node.

julia> print_expression(io, o::typeof(-->), ps) = parenthesize(io) do
           print_proposition(io, first(ps))
           print(io, " ")
           show(io, "text/plain", o)
           print(io, " ")
           print_proposition(io, last(ps))
       end;ERROR: UndefVarError: `-->` not defined
julia> @atomize p --> qERROR: UndefVarError: `-->` not defined
julia> evaluate(::typeof(-->), ps) = first(ps) → last(ps);ERROR: UndefVarError: `-->` not defined
julia> @atomize print_table(p --> q)ERROR: UndefVarError: `-->` not defined
julia> @atomize fold(𝒾, (-->) => ())ERROR: KeyError: key PAndQ.AbstractSyntaxTree(:-->) not found

This error says to implement Associativity. This function is used to determine which direction to fold.

julia> Associativity(::typeof(-->)) = Left;ERROR: UndefVarError: `-->` not defined
julia> @atomize fold(𝒾, (-->) => ())ERROR: KeyError: key PAndQ.AbstractSyntaxTree(:-->) not found

This error says to implement initial_value. This function is used to determine the init parameter when folding.

julia> initial_value(::typeof(-->)) = ⊤;ERROR: UndefVarError: `-->` not defined
julia> @atomize fold(𝒾, (-->) => ())ERROR: KeyError: key PAndQ.AbstractSyntaxTree(:-->) not found
julia> @atomize fold(𝒾, (-->) => (p, q, r))ERROR: KeyError: key PAndQ.AbstractSyntaxTree(:-->) not found

Ternary

This is a lazily evaluated conditional operator.

julia> const conditional = Operator{:conditional}();ERROR: TypeError: in Type{...} expression, expected UnionAll, got Type{PAndQ.Interface.Operator}
julia> symbol(::typeof(conditional)) = "?";ERROR: UndefVarError: `conditional` not defined
julia> conditionalERROR: UndefVarError: `conditional` not defined
julia> Evaluation(::typeof(conditional)) = Lazy;ERROR: UndefVarError: `conditional` not defined
julia> print_expression(io, o::typeof(conditional), ps) = parenthesize(io) do print_proposition(io, ps[1]) print(io, " ? ") print_proposition(io, ps[2]) print(io, " : ") print_proposition(io, ps[3]) end;ERROR: UndefVarError: `conditional` not defined
julia> @atomize conditional(p, q, r)ERROR: MethodError: objects of type PAndQ.AbstractSyntaxTree are not callable
julia> function evaluate(::typeof(conditional), ps) p, q, r = ps (p → q) ∧ (p ∨ r) end;ERROR: UndefVarError: `conditional` not defined
julia> @atomize print_table(conditional(p, q, r))ERROR: MethodError: objects of type PAndQ.AbstractSyntaxTree are not callable