Dynamic environments

Previously, I defined a tool and an environment interface. It looks like this:

use serde_json::*;

pub trait Tool : Send+Sync {
    fn invoke_json(&self, input: Value) -> Result<Value, Value>;
}

What I'm working on here is a way to dynamically interact with tools in Rust. I need a way to retrieve a tool given its name. To do this, we can use an 'environment', which is just that:

pub trait Environment : Send {
    fn get_json_tool(&self, name: &str) -> Result<Box<Tool>, RetrieveToolError>;
}

There's a problem here. The obvious way to use these tools is via a scripting environment. In such an environment we'll want a couple of things that are not possible with these two interfaces.

The first thing is a way for a tool to find other tools by name in a dynamic manner. We can write a tool that retrieves other tools from an environment if it already knows which environment it is going to retrieve from, but we have no way to supply an environment to a tool. For example, imagine a sort tool where we want to be able to specify the ordering function.

The second problem is similar: if we consider the scripting environment as a tool in itself we might want a way to define a new tool in an environment. The environment interface is read-only so this seems to be a difficult problem.

The solution to both these problems is the same. We keep the basic environment interface immutable, but the Tool definition becomes this:

use serde_json::*;

pub trait Tool : Send+Sync {
    fn invoke_json(&self, input: Value, environment: &Environment) -> Result<Value, Value>;
}

This solves the first problem in a manner that's fairly straightforward. We can retrieve 'dynamic' tools from the environment passed in to the tool. Tools can still track their own environment for more static references.

The problem of dynamically defining tools can also be addressed, in a way that's less obvious but which also gives us a way for tools to interact. Recall that a tool can have a reference to an environment other than the one that's passed in. This reference doesn't just have to be the interface: a tool could be written that is initialised with a dynamic environment and copies a tool reference from the environment that's passed in to this parent environment.

This makes it possible to alias tools quickly, but it also provides a way for other tools, including our hypothetical scripting language, to define new tools. New tools are defined by creating an environment that contains just the new tool, then looking up the definition tool and calling it with the environment and the name of the tool that's being defined. It copies the tool into its dynamic environment.

This is nice in that we can later on remove the redefinition tool to make everything more static again. These operations - two interfaces with one function each - are sufficient to define an entire interactive scripting system.