Lurch Deductive Engine

source

binding-interface.js


/**
 * Many mathematical expressions include "dummy variables," also called "bound
 * variables."  For example, $\sum_{i=1}^n a_i$ uses $i$ as a "dummy" or "bound"
 * variable.  Similarly, $\int x^2\;dx$ has $x$ as a bound varibale.  There are
 * many other examples, including (but not limited to) the following.
 * 
 *  * indexed sums, products, unions, and intersections throughout mathematics
 *  * set-builder notation, e.g., $\left\\{ x+y \mid 0 < x < y \right\\}$
 *  * existentially and universally quantified expressions, such as
 *    $\forall x > 0, ~|x|=x$.
 *  * $\lambda$ expressions from computer science, as in $\lambda x.x$
 * 
 * We say that such expressions "bind" the dummy/bound variable, and we have two
 * types of them in Lurch: {@link BindingExpression BindingExpressions} and
 * {@link BindingEnvironment BindingEnvironments}.  In order to factor the
 * common attributes out of those two classes, we implement the notion of
 * binding as what some languages call an "interface," here.  We place it in the
 * {@link BindingInterface BindingInterface} namespace, and it can be added to
 * any class by calling the {@link BindingInterface.addTo addTo()} function.
 * 
 * Each of the example types of expressions given above include not only a body
 * in which a variable is bound, but also an operator applied to that body.  For
 * example, in $\bigcup_i A_i$, we not only have the body $A_i$ in which $i$ is
 * bound, but we are also applying the union operator to that body.  However, we
 * do not include the operator in the binding itself, because not all cases use
 * an operator; in particular, {@link BindingEnvironment BindingEnvironments} do
 * not have an operator.
 * 
 * Consequently, when encoding something like $\bigcup_i A_i$, we would encode
 * the $A_i$ as the body of a {@link BindingExpression BindingExpression} with a
 * bound variable list of length 1, containing just $i$, and then wrap that
 * {@link BindingExpression BindingExpression} in an
 * {@link Application Application} of the $\cup$ {@link Symbol Symbol}.  One
 * might construct it with code like the following example (though this is not
 * the only possible way to encode such an idea).
 * 
 * ```js
 * new Application(
 *     new LurchSymbol( '⋃' ),
 *     new BindingExpression(
 *         new LurchSymbol( 'i' ),
 *         new Application(
 *             new LurchSymbol( 'subscript' ),
 *             new LurchSymbol( 'A' ),
 *             new LurchSymbol( 'i' )
 *         )
 *     )
 * )
 * ```
 * 
 * Bindings were inspired by a structure of the same name defined in
 * {@link https://openmath.org/about/ the OpenMath Standard}, but the reader
 * is not expected to read that standard; we define in this documentation our
 * version of what a binding is, both for {@link Expression Expressions} and
 * {@link Environment Environments}.
 * 
 * A binding is defined by a sequence of $n$ children satisfying the following
 * requirements.  Most functions in this interface depend upon these
 * requirements being satisfied in order for the functions to work correctly.
 * Constructors for classes implementing this interface should check to ensure
 * that these requirements are satisfied at construction time, and provide
 * documentation stating that clients should not violate these requirements with
 * tree manipulation later.
 * 
 *  * The first $n-1$ children will be the sequence of bound variables.  There
 *    must be at least one bound variable, so we have that $n>1$.  Each bound
 *    variable must be a {@link Symbol Symbol}.  You can use
 *    {@link BindingInterface.allAreSymbols allAreSymbols()} to help verify the
 *    requirement.
 *  * The last child is called the {@link BindingInterface.body body()}.  This
 *    is what "sits inside" the binding, such as the integrand in an integral,
 *    the summand in a summation, the inner statement in a quantification, etc.
 *    Although it is most commonly an {@link Expression Expression}, it does not
 *    have to be; a binding with an {@link Expression Expression} body is a
 *    {@link BindingExpression BindingExpression} and a binding with an
 *    {@link Environment Environment} body is a
 *    {@link BindingEnvironment BindingEnvironment}.  Binding environments are
 *    typically subproofs that begin with the declaration of one or more
 *    arbitrary variables, which act as bound within that subproof.
 * 
 * The API provided in this namespace can be added to any class by calling the
 * {@link BindingInterface.addTo addTo()} function on the class's prototype.
 * Then all the functions documented below will be available in members of that
 * class.
 * 
 * @namespace BindingInterface
 */

import { Symbol as LurchSymbol } from './symbol.js'

/**
 * As documented at the top of this page, the body of a binding is the last
 * child of the {@link MathConcept MathConcept}, which will either be an
 * {@link Expression Expression} or {@link Environment Environment}, depending
 * on the type of binding.  This function returns that body.
 * 
 * @returns {MathConcept} the body of this binding
 *
 * @memberof BindingInterface
 * @alias BindingInterface.body
 * 
 * @see {@link BindingInterface.boundSymbols boundSymbols()}
 */
export const body = function () { return this.lastChild() }

/**
 * As documented at the top of this page, the bound symbols in a binding are all
 * the children except the last, and they should be all instances of the
 * {@link Symbol Symbol} class.  This function returns those children in an
 * array, in the same order they appear as children of this object.  The actual
 * children are returned, not copies, and they are {@link Symbol Symbol}
 * instances, not just their names as strings.
 * 
 * Just in case the client has manipulated this object since construction time,
 * thus causing one of its initial children to be something other than a
 * {@link Symbol Symbol}, this function filters its return list to include only
 * instances of the {@link Symbol Symbol} class.  Note that this routine does
 * not need to provide any guarantees in such a case, because the client has
 * invalidated its structure as a binding.  However, in such a case, for a
 * {@link MathConcept MathConcept} with $n$ children, this function may return
 * fewer than $n-1$ results.
 * 
 * @returns {Symbol[]} an array of {@link Symbol Symbols} bound by this
 *   binding, in the order they appear as the children of this
 *   {@link MathConcept MathConcept}
 *
 * @memberof BindingInterface
 * @alias BindingInterface.boundSymbols
 * 
 * @see {@link BindingInterface.boundSymbols boundSymbols()}
 * @see {@link BindingInterface.boundSymbolNames boundSymbolNames()}
 */
export const boundSymbols = function () {
    return this.allButLastChild()
               .filter( child => child instanceof LurchSymbol )
}

/**
 * This function behaves exactly like {@link BindingInterface.boundSymbols
 * boundSymbols()}, but instead of returning actual {@link Symbol Symbol}
 * instances, it returns only their names, as strings, in the same order.
 * 
 * @returns {string[]} an array of the names of the symbols bound by this
 *   binding, in the order they appear as the children of this
 *   {@link MathConcept MathConcept}
 *
 * @memberof BindingInterface
 * @alias BindingInterface.boundSymbolNames
 * 
 * @see {@link BindingInterface.boundSymbols boundSymbols()}
 */
export const boundSymbolNames = function () {
    return this.boundSymbols().map( symbol => symbol.text() )
}

/**
 * Does this binding bind a specific symbol?  You can provide the symbol to this
 * function (in any of the formats documented below) and this function will do a
 * search and return true if it finds such a symbol among this binding's bound
 * symbols, and false if not.
 * 
 * If the parameter is omitted, then this function simply returns the constant
 * true, indicating that it is a binding type of
 * {@link MathConcept MathConcept}, that is, it has the capacity to bind
 * {@link Symbol Symbols}.
 * 
 * @param {Symbol|string|any} [symbol] - the symbol to search for, either as an
 *   instance of class {@link Symbol Symbol}, or as a string containing just the
 *   symbol's name, or as any other type of data that will be converted to a
 *   string and treated as the name to search for; this parameter can be omitted
 *   to test whether this {@link MathConcept MathConcet} is a binding.
 * @returns {boolean} whether this binding binds a {@link Symbol Symbol}
 *   matching the parameter
 *
 * @memberof BindingInterface
 * @alias BindingInterface.binds
 */
export const binds = function ( symbol ) {
    if ( typeof( symbol ) == 'undefined' ) return true
    const name = symbol instanceof LurchSymbol ? symbol.text() : `${symbol}`
    return this.boundSymbols().some( bound => bound.text() == name )
}

/**
 * Document function here
 * 
 * @returns {type} foo bar baz
 *
 * @memberof BindingInterface
 * @alias BindingInterface.allAreSymbols
 */
export const allAreSymbols = array =>
    array.every( element => element instanceof LurchSymbol )

/**
 * Document function here
 * 
 * @returns {type} foo bar baz
 *
 * @memberof BindingInterface
 * @alias BindingInterface.addTo
 */
export const addTo = prototype => {
    prototype.body = body
    prototype.boundSymbols = boundSymbols
    prototype.boundSymbolNames = boundSymbolNames
    prototype.binds = binds
}

export default { allAreSymbols, addTo }