This is an old revision of the document!
Table of Contents
Modular Programming
In this tutorial chapter, we introduce AmbientTalk's language features that have to do with writing modular programs. The term “modular” can be taken quite broadly in this context. By “modular” programming, we here mean programs hierarchically structured into multiple files and objects composed from multiple so-called “trait” objects. We also describe AmbientTalk's lightweight support for classifying objects and how this enables exception handling based on “types”.
Namespaces and the lobby
In AmbientTalk, programs are primarily partitioned into multiple files. Hence, a file can be regarded as the most coarse-grained module in an AmbientTalk system. Each file has its own namespace: a “global” variable is only globally visible in the file in which it has been defined. There is only one exception to this rule: definitions in the “init” file (which is by default the file at/init/init.at
available in the system library) are considered global to all files.
Namespaces
In order for AmbientTalk programs to use code defined in other files, a file loading mechanism is required. AmbientTalk provides the concept of a namespace object to perform this task. A namespace object is an object which is internally connected to a directory path on the local file system (how a namespace object is tied to this path will be explained later). By accessing a namespace object's slots, files can be loaded. Consider, for example, a namespace object stored in the variable lib
which is internally tied to the directory /home/ambienttalkuser/examples
. Also consider that in the examples
directory, there exists a file named math.at
with the following contents:
object: { def factorial(n) { if: (n = 0) then: { 1 } else: { n*factorial(n-1) } }; def fib(n) { if: (n <= 1) then: { n } else: { fib(n-1)+fib(n-2) } }; };
This file can now be loaded by means of the lib
namespace object as follows:
def mathModule := lib.math; system.println(mathModule.factorial(5));
When the namespace object's math
slot is accessed for the first time, the namespace object checks whether its encapsulated directory contains:
- a file named
math.at
- a subdirectory named
math
In this example, there exists a file /home/ambienttalkuser/examples/math.at
. In this case, the file is loaded and the result of evaluating the code in that file is bound to the math
slot. Recall that the math.at
file defined one object with two methods. The above code snippet shows how these methods can now simply be used in the context of another file.
If the math
slot is accessed a second time, the math.at
file will not be loaded again. Rather, the math
slot simply contains the return value from the loaded code. Hence, namespaces provide the desirable semantics that a file is loaded only the first time it is required. If the math.at
file would not have existed, the call lib.math
may still have succeeded if there would have existed a subdirectory named math
in lib
's directory. In that case, lib
's math
slot would be bound to a new namespace object encapsulating the directory /home/ambienttalkuser/examples/math
.
math.at
file later decides to change the file structure of his library by creating a directory named math
and by placing the code of each function in a separate file (named factorial.at
and fib.at
). The code defined above would not be affected: the expression lib.math
would then evaluate to a namespace object for the math
subdirectory, and the expression mathModule.factorial
would load the file factorial.at
which returns a closure that is immediately invoked.
Obviously, this approach works both ways: if the library designer had started out organizing his project using subdirectories and multiple files, he can always restructure the library's file organization without breaking the interface to clients by replacing a subdirectory with an AmbientTalk file that evaluates to an object whose public slot names correspond to the file names of the original subdirectory.
We have yet to explain how namespaces are initially tied to directories. One possibility is to define a root
variable which would be bound to the “/” directory (i.e. the root directory of the file system). However, to load the contents of the file /home/ambienttalkuser/examples/math.at
, this would require writing:
root.home.ambienttalkuser.examples.math
The downside of such as scheme is that the absolute path name of the math.at
file is hardcoded in the AmbientTalk application. If we would later decide to move the file, our AmbientTalk code using that file would be broken! To avoid these problems, AmbientTalk provides the lobby
object. The lobby
is an object defined in the top-level scope whose slots are bound to namespace objects. The names of the slots, and the directory paths of the namespace objects bound to them are not provided in AmbientTalk directly, but rather when starting the AmbientTalk interpreter.
The interactive ambienttalk shell (iat
) has a command-line parameter called the object path. This argument is a list of “name=path” entries separated by colons. For example, in order to create a namespace object called lib
which is bound to /home/ambienttalkuser/examples
, it suffices to start iat
as follows:
iat -o lib=/home/ambienttalkuser/examples
Whenever a new actor is created by the AmbientTalk interpreter, it uses the entries in the object path to initialize the lobby
object of the actor. Hence, by starting up the interpreter as shown above, executing lobby.lib
in any actor returns a reference to a namespace object bound to the examples
directory. This effectively removes absolute path names from AmbientTalk source code and even enables you to quickly reconfigure AmbientTalk code to use other libraries by loading the code using another object path.
lobby
variable named /
. Hence, rather than writing lobby.lib
, it is also possible to write /.lib
which is often more convenient, although perhaps a bit more cryptic.
==== The current namespace ====
AmbientTalk provides a slot named ~
which is always bound to the “current namespace”, the namespace in which the current file is contained. This is useful for loading files in a relative manner. For example, in the math.at
file defined above, evaluating ~
would result in the lib
namespace. Hence, if there would be a file named discretemath.at
in the directory /home/ambienttalkuser/examples
, then the math.at
file could load it by writing:
def discreteMathModule := ~.discretemath;
Loading a module this way is useful because the author of math.at
does not necessarily know of the lib
namespace. Also, using lobby.lib
introduces the implicit requirement that an object path for lib
must be given. The slot ~
is always well-defined, so using it does not introduce additional dependencies.
===== Importing objects =====
explain: import native, using import for importing external file definitions. Warning: importing from namespaces
===== Objects as traits =====
explain: using import as an object-composition mechanism: concepts behind traits (required/provided interface), aliasing, exclusion.
===== Classifying objects using stripes =====
explain: what are stripes? what kind of objects are they, stripe subtyping, stripe test, what default stripes exist
===== Exception Handling =====
explain: raise, try-catch and variants, first-class handlers, role of stripes, interface of an exception object