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
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, arity, dual, evaluate, initial_value, parenthesize, print_expression, symbol
julia> using PAndQ, .Interface
Nullary
This is a renamed tautology
operator. First, define an Operator
. If possible, this should be a const
ant 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";
julia> truth
truth
julia> truth()
ERROR: InterfaceError: implement `PAndQ.Interface.Evaluation` for `PAndQ.Interface.Operator{:truth}()`
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: InterfaceError: implement `arity` for `PAndQ.Interface.Operator{:truth}()`
The error says to implement arity
. This function is used to construct a node in a syntax tree.
julia> arity(::typeof(truth)) = 0;
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)) = show(io, "text/plain", o);
julia> print_table(truth())
ERROR: InterfaceError: implement `evaluate` for `PAndQ.Interface.Operator{:truth}()` with `0` propositions
The error says to implement evaluate
. This function is used to specify the semantics of an operator.
julia> evaluate(::typeof(truth)) = ⊤;
julia> print_table(truth())
┌───────┐ │ truth │ ├───────┤ │ ⊤ │ └───────┘
Unary
This is an eagerly evaluated not
operator.
julia> const negate = Operator{:negate}();
julia> symbol(::typeof(negate)) = "negate";
julia> negate
negate
julia> Evaluation(::typeof(negate)) = Eager;
julia> evaluate(::typeof(negate), p) = evaluate(¬, p);
julia> @atomize negate(¬p)
p
julia> @atomize print_table(negate(p))
┌───┬────┐ │ p │ ¬p │ ├───┼────┤ │ ⊤ │ ⊥ │ │ ⊥ │ ⊤ │ └───┴────┘
Binary
This is an imply
operator represented by the -->
symbol.
julia> const if_then = --> = Operator{:if_then}();
julia> symbol(::typeof(-->)) = "-->";
julia> -->
-->
julia> Evaluation(::typeof(-->)) = Lazy;
julia> arity(::typeof(-->)) = 2;
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(-->), p, q) = parenthesize(io) do print_proposition(io, p) print(io, " ") show(io, "text/plain", o) print(io, " ") print_proposition(io, q) end;
julia> @atomize p --> q
p --> q
julia> evaluate(::typeof(-->), p, q) = p → q;
julia> @atomize print_table(p --> q)
┌───┬───┬─────────┐ │ p │ q │ p --> q │ ├───┼───┼─────────┤ │ ⊤ │ ⊤ │ ⊤ │ │ ⊥ │ ⊤ │ ⊤ │ ├───┼───┼─────────┤ │ ⊤ │ ⊥ │ ⊥ │ │ ⊥ │ ⊥ │ ⊤ │ └───┴───┴─────────┘
julia> @atomize fold(𝒾, (-->) => ())
ERROR: InterfaceError: implement `PAndQ.Interface.Associativity` for `PAndQ.Interface.Operator{:if_then}()`
This error says to implement Associativity
. This function is used to determine which direction to fold
.
julia> Associativity(::typeof(-->)) = Left;
julia> @atomize fold(𝒾, (-->) => ())
ERROR: InterfaceError: implement `initial_value` for `PAndQ.Interface.Operator{:if_then}()`
This error says to implement initial_value
. This function is used to determine the init
parameter when fold
ing.
julia> initial_value(::typeof(-->)) = Some(⊤);
julia> @atomize fold(𝒾, (-->) => ())
⊤
julia> @atomize fold(𝒾, (-->) => (p, q, r))
(p --> q) --> r
Ternary
This is a lazily evaluated conditional operator.
julia> const conditional = Operator{:conditional}();
julia> symbol(::typeof(conditional)) = "?";
julia> conditional
?
julia> Evaluation(::typeof(conditional)) = Lazy;
julia> arity(::typeof(conditional)) = 3;
julia> print_expression(io, o::typeof(conditional), p, q, r) = parenthesize(io) do print_proposition(io, p) print(io, " ? ") print_proposition(io, q) print(io, " : ") print_proposition(io, r) end;
julia> @atomize conditional(p, q, r)
p ? q : r
julia> evaluate(::typeof(conditional), p, q, r) = (p → q) ∧ (p ∨ r);
julia> @atomize print_table(conditional(p, q, r))
┌───┬───┬───┬───────────┐ │ p │ q │ r │ p ? q : r │ ├───┼───┼───┼───────────┤ │ ⊤ │ ⊤ │ ⊤ │ ⊤ │ │ ⊥ │ ⊤ │ ⊤ │ ⊤ │ ├───┼───┼───┼───────────┤ │ ⊤ │ ⊥ │ ⊤ │ ⊥ │ │ ⊥ │ ⊥ │ ⊤ │ ⊤ │ ├───┼───┼───┼───────────┤ │ ⊤ │ ⊤ │ ⊥ │ ⊤ │ │ ⊥ │ ⊤ │ ⊥ │ ⊥ │ ├───┼───┼───┼───────────┤ │ ⊤ │ ⊥ │ ⊥ │ ⊥ │ │ ⊥ │ ⊥ │ ⊥ │ ⊥ │ └───┴───┴───┴───────────┘