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
String
s,
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 Symbol
classname> 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.