The ExecutionOutputLink is a type of FunctionLink that allows the execution of arbitrary scheme or python code, returning an Atom as a return value. They should be contrasted with EvaluationLinks, which can evaluate arbitrary scheme or python code, but return a TruthValue instead of an Atom.
Because ExecutionOutputLinks can execute arbitrary code, they behave like "black boxes" and prevent any sort of reasoning or reduction from being applied to them. The AtomSpace also supports a number of pre-defined "clear-box" links, such as PlusLink and TimesLink, for which reduction can be performed. See FunctionLink and cog-reduce! for additional discussion of clear-box link types.
ExecutionOutputLinks can be executed with the
cog-execute! function. They are executed automatically by the pattern matcher when they are used in the implicand of a BindLink. They are also used in the OpenPsi and procedural parts of OpenCog.
If one has a GroundedSchemaNode S, then
ExecutionOutputLink S A
indicates the output obtained by applying S to the argument (potentially argument-list) A.
ExecutionOutputLink + (3 2)
would have value 5, since 3+2=5. Which could be represented in the atomspace by
SimilarityLink <1> NumberNode 5 ExecutionOutputLink GroundedSchemaNode + ListLink NumberNode 3 NumberNode 2
For more information, see Proceedure Nodes.
This is not to be confused with ExecutionLink, which connects the inputs to the output of a function (or a procedure, if it doesn't have output). For instance one may rewrite the example above using ExecutionLink as follows:
ExecutionLink <1> GroundedSchemaNode + ListLink NumberNode 3 NumberNode 2 NumberNode 5
A way to automatically create ExecutionLinks from ExecutionOutputLinks is given below.
The ExecutionOutputLink can be used either with a GroundedSchemaNode, which specifies a callback into scheme, Python or C++ code; a DefinedSchemaNode (defined via a DefineLink; or a LambdaLink, which defies a function body.
Thus, for example:
ExecutionOutputLink GroundedSchemaNode func ListLink SomeAtom val_1 OtherAtom val_2
would cause func(val_1, val_2) to be called. Upon execution, it is expected that an atom is produced as a "return value".
In principle, the schema name could be any URL whatsoever. Currently, only the c++:, scm: and py: URLs for c++ function, scheme and python scripts work. The idea of supporting http: style URL's, for JSON or SOAP or whatever, is just a wild-eyed idea, at the moment.
GroundedSchemaNode in Scheme
For example, suppose one had defined a scheme function
(define (do-stuff handle1 handle2) ( ... ))
Then the evaluation of the following
ExecutionOutputLink GroundedSchemaNode "scm:do-stuff" ListLink SomeNode arg_1 OtherNode arg_2
would cause (do-stuff arg_1 arg_2) to be evaluated.
GroundedSchemaNode in Python
Python is supported, as well:
def my_cat(a,b): return ConceptNode(a + b)
The above, after being loaded, ca be invoked as:
ExecutionOutputLink GroundedSchemaNode "py: my_cat" ListLink ConceptNode "Hello" ConceptNode ", World"
which will return the atom
ConceptNode "Hello, World"
LambdaLink and DefineLink examples
The following example shows how to execute a LambdaLink:
(ExecutionOutputLink (LambdaLink (VariableList (VariableNode "$X") (VariableNode "$Y")) (PlusLink (VariableNode "$X") (TimesLink (VariableNode "$Y") (NumberNode 10)))) (ListLink (NumberNode 2) (NumberNode 4)))
The above works and is supported syntax today. Upon execution (with cog-execute!) it will return
The lambda can be give a name with DefineLink:
(DefineLink (DefinedSchemaNode "the answer") (LambdaLink (VariableList (VariableNode "$X") (VariableNode "$Y")) (PlusLink (VariableNode "$X") (TimesLink (VariableNode "$Y") (NumberNode 10)))))
ad invoked with that name:
(ExecutionOutputLink (DefinedSchemaNode "the answer") (ListLink (NumberNode 2) (NumberNode 4)))
The above works and is supported syntax today (see SCMExecutionOutputUTest.cxxtest).
One convenient way of using the ExecutionOutputLink is with the pattern matcher: this uses a BindLink to specify a set of variables, the pattern to be matched and the execution link to be called for the grounded variables matching the pattern. See the BindLink page for examples.
ExecutionOutputLinks are evaluated when they are found on the implication side of a BindLink. In this case, the pattern matcher will substitute grounded values for all variables, and then call func. If func returns an atom, then that returned atom is taken as the grounding (or output) of the ExecutionOutputLink. That is, the returned atom is substituted for the ExecutionOutputLink in the implicand.
When used in BindLinks, the ExecutionOutputLinks should not appear in the predicate side of the implication. In such a case, if the goal of executing the function is to determine whether a given pattern matches or not (within the BindLink), virtual links should be used instead.
Execution of ExecutionOutputLinks can be accomplished directly from the C++ interfaces by calling the static C++ method
ExecutionLink::do_execute() (in the opencog/execution directory.) For scheme, see the
cog-execute! function. For Python, use the scheme_wrapper.
Recording the output
Sometimes, one may want to record the output of an ExecutionOutputLink in an ExecutionLink. How could this be done? There is an easy, if slightly verbose solution: just write this:
ExecutionLink SchemaNode "did something" ListLink ArgAtoms "args" ExecutionOutputLink GroundedSchemaNode "scm:do-something" ListLink ArgAtoms "args"
Then, upon evaluation, the atom that is generated by the scheme function "do-something" is used to replace ExecutionOutputLink with the result. So for example, the following scheme code:
(define (do-something atom) (AttractionLink atom atom atom)) (cog-execute! (ExecutionLink (SchemaNode "did something") (ListLink (ConceptNode "args")) (ExecutionOutputLink (GroundedSchemaNode "scm:do-something") (ListLink (ConceptNode "args")))))
will return the following:
(ExecutionLink (SchemaNode "did something") (ListLink (ConceptNode "args")) (AttractionLink (ConceptNode "args") (ConceptNode "args") (ConceptNode "args")))
Similarly, the following:
(cog-execute! (ExecutionLink (SchemaNode "sum of stuff") (ListLink (NumberNode 2) (NumberNode 3)) (PlusLink (NumberNode 2) (NumberNode 3))))
will result in the following output:
(ExecutionLink (SchemaNode "sum of stuff") (ListLink (NumberNode "2.000000") (NumberNode "3.000000") ) (NumberNode "5.000000") )