The ExecutionOutputLink is a type of FunctionLink that allows the execution of arbitrary scheme, python, haskell and Atomese code, returning either a Value or an Atom as a return value. They should be contrasted with EvaluationLinks, which can evaluate arbitrary code, but are limited to returning only TruthValues instead of generic Values or Atoms.
When ExecutionOutputLinks are paired with GroundedSchemaNodes, they behave like "black boxes" whose inner working are completely opaque to any sort of symbolic reasoning, inference or reduction that could be applied. This is in contrast to, for example, the arithmetic links, such as PlusLink and TimesLink, which do allow symbolic reasoning and manipulation. Thus, in general, the use of ExecutionOutputLinks is strongly discouraged, except in on particular domain: to interface with external systems for sensory systems or for control (e.g. for robot control, video/audio perception systems, etc.)
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.
ExecutionOutputLink schema args
represents the application of the function-symbol
schema to the argument-list
args. That is, it provides a graphical representation of a function call (because it is, after all Atomese). However, it can also be directly executed to actually perform the indicated computation.
The ExecutionOutputLink is in many ways effectively equivalent to a PutLink, when used with LambdaLink and DefinedSchemaNode. The PutLink is meant to to represent a beta-reduction operation, or a beta-reduction step. However, when a PutLink is executed, not only is the beta reduction performed, but also the resulting Atom is executed as well.
The PutLink does NOT support GroundedSchema; only the ExecutionOutputLink can be used with GroundedSchema. This is because the PutLink plugs terms into variables, and then executes the result. GroundedSchema have no variables, so there is nothing to "plug into", there is nothing to beta-reduce.
For more information, see Procedure Nodes.
The ExecutionOutputLink is not to be confused with the ExecutionLink, which yokes together the input and the output of a function. 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
(cog-execute! (ExecutionOutputLink (LambdaLink (VariableList (VariableNode "$X") (VariableNode "$Y")) (PlusLink (VariableNode "$X") (TimesLink (VariableNode "$Y") (NumberNode 10)))) (ListLink (NumberNode 2) (NumberNode 4))))
Using a PutLink instead of the ExecutionOutputLink will yield the same result.
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)))))
and invoked with that name:
(ExecutionOutputLink (DefinedSchemaNode "the answer") (ListLink (NumberNode 2) (NumberNode 4)))
See multiple tutorials in the /examples directory.
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 executed when they are found on the implication side of a BindLink. In this case, the pattern matcher will substitute grounded terms 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
ExecutionOutputLink::do_execute() (in the opencog/atoms/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") )