Symbols represent identifiers, and do not need much functionality. Scheme needs to be able to convert them to and from Scheme strings, and they need to be “interned” (which means that there is a global table to ensure that there is a unique symbol for a given identifier). Symbols are immutable and have no accessible internal structure.
Originally, Scheme symbols were represented using interned Java
Strings,
but they are now represented using a Symbol class:
A Symbol is stateless:
Common Lisp-style “value”, “function” and
“property list” bindings are not part of the Symbolclassname> itself, but
looked up in the current Environment.
class Symbol
{
protected String name;
Namespace namespace;
}
A Symbol has two components:
The name is its printable (local) name.
In an uninterned Symbol
the namespace is null.
But normally a Symbol is interned
in a Namespace, which is a mapping from
printable name to Symbol objects (whose
name field is the printable name and whose
namespace points
back to that Namespace.)
A Namespace is similar to a
Common Lisp package.
class Namespace
{
protected String name;
public static final Namespace EmptyNamespace;
public Symbol lookup(String key) { ... }
}
Most commonly the namespace of a
Symbol is Namespace.EmptyNamespace,
whose name is the empty string "".
class Environment
{ ...;
}
An Environment is a mapping from symbols to bindings,
which are locations that can hold a value.
It is used for the bindings of the user top-level.
There can be multiple top-level Environments, and
an Environment can be defined as an extension
of an existing Environment.
The latter feature is used to implement the various standard
environment arguments that can be passed to eval.
Nested environments were also implemented to support threads,
and fluid bindings (even in the presence of threads).
An Environment is actually a 2-dimensional
mapping from a pair of a Symbol and
an arbitrary property object to locations.
A normal value-lookup is done using a null property.
In a language like Emacs Lisp or Common Lisp, which has a separate
namespace for functions, you'd get the function binding
of a Symbol by doing a lookup
using Symbol and specifying the
constant EnvironmentKey.FUNCTION as the property.
Each Kawa language defines
an Environment of pre-defined definitions.
(This Environment is immutable once it's
been initialized.)
This is the value of the getLangEnvironment() method
of the Language object.
There may be multiple Language objects in use, but the
current “context language” is that
returned by Language.getDefaultLanguage().
There is a special magic BuiltinEnvironment
which forwards all lookups
to Language.getDefaultLanguage().getLangEnvironment().
In addition, the context has a “user Environment”. By default,
this is per-thread (and its name is set from the thread-name).
If the thread is a RunnableClosure then
the thread's Environment will inherit
from the parent (originating) thread's Environment;
otherwise the user environment
inherits from BuiltinEnvironment (at least by default).
Thus a name lookup will first search the user environment,
then that of its parent threads (if it was
created as a RunnableClosure),
and then the BuiltinEnvironment
- i.e. the language builtins.
Thus you can switch the current language but still have
access to the user definitions.