Eric Kidd <eric.kidd@pobox.com>

Conversion from DocBook 3.1 (SGML) to DocBook 4.2 (XML).
: Brent Fulgham

Abstract

Dylan is a object-oriented dynamic language
designed for efficient compilation. It uses an algebraic infix syntax
similar to Pascal or C, but supports an object model not unlike the
Common Lisp Object System (CLOS).

This tutorial is written primarily for those with solid
programming experience in C++ or another object-oriented static
language. It provides a gentler introduction to Dylan than does the Dylan Reference Manual , although it refers to the latter book frequently.


Table of Contents

1. Why Dylan? Dynamic vs. Static Languages Functional Languages Algebraic Infix Syntax Object rientation Garbage Collection Why Not Dylan?
2. Expressions & Variables Naming Conventions True and False The Nature of Variables Assignment, Equality and Identity parallel Values Type Declarations Module Variables and Constants
3. Methods & Generic functions parameters & parameter Lists Return Values Bare Methods Local Methods Generic functions Keyword Arguments
4. Objects Built-In Classes Slots Getters and Setters Generic functions and Objects Initializers Abstract Classes and Overriding Make
5. Multiple Dispatch Dispatching on Specific Objects
6. Modules & Libraries Simple Modules Import Options Libraries A Sample Set of Libraries Sealing
7. Conditions Blocks

Chapter 1. Why Dylan?

What earthly reason could there be for learning yet
another
computer language? And why should that language be Dylan?

Dylan has an interesting combination of features. It is a dynamic
language, but is designed to perform nearly as well as a static language.
It is a functional language—like
Scheme or TCL—but uses an algebraic infix syntax similar to C’s.

Dylan is object-oriented from the ground up, supports multiple
inheritence and exceptions, implements
multiple dispatch
, and collects garbage.

Dynamic vs. Static Languages

Static languages need to know the type
of every variable at compile time. Examples of static languages include
C, Pascal, and Eiffel. Code written in static languages typically
compiles efficiently, and strong type-checking at compile-time reduces
the risk of errors.

Dynamic languages allow the programmer to
create variables without explicitly specifying the type of information
they contain. This simplifies prototyping and cleans up certain kinds
of object oriented code. Typical dynamic languages include LISP, Perl,
and SmallTalk.

Dylan provides a good balance between the advantages of static
and dynamic languages. The programmer may choose to specify or omit
type declarations as desired. Code using explicit variable types can
be compiled very efficiently, and type mismatch errors can be caught
at compile time. Code omitting those type declarations gains the
flexibility of a dynamic language.

Functional Languages

Functional languages, such as LISP,
Scheme and to a large extent TCL, view an entire program as one large
function to be evaluated. Expressions, statements and even control
structures all return values, which may in turn be used as arguments
elsewhere.

Dylan is a functional language, permitting programmers to write
functions like the following:

define method shoe-size(person :: <string>)
  if (person = "Larry")
    14;
  else
    11;
  end if;
end method;

The function shoe-size has one argument,
a string, and an untyped return value. (If this function didn’t link
against external code, the compiler could easily infer the return
type.) If person equals "Larry",
then the if statement evaluates to 14, otherwise
it returns 11. Since no other statements follow the if
, its return value is used as the return value of the entire
function.

The same function could also have been written as follows, in a
more imperative idiom:

define method shoe-size(person :: <string>)
  let the-size = 11;
  if (person = "Joe")
    the-size := 14;
  end if;
  the-size;
end method;

Algebraic Infix Syntax

Languages based on LISP typically use a notation called
fully-parethesized prefix syntax. This consists
of innumerable nested paretheses, as seen in the following Scheme
version of the shoe-size function:

(define (shoe-size person)
  (if (equal? person "Joe")
    14
    11))

This has a certain elegance, but takes some time to learn to
read. Dylan, as shown in the previous
section
, uses a syntax similar to those of C and Pascal.

Object Orientation

Unlike many other object-oriented languages, Dylan uses objects
for every data value. Integers and strings are objects, as are
functions and classes themselves.

Dylan’s design makes this reasonably efficient. Compile-time
analysis and explicit type
declarations
allow the compiler to optimize away most of the
overhead. Other language features permit the programmer to mark
certain classes as sealed, that is, inelligible
for further subclassing.

Dylan’s object model, detailed in the following sections of this
tutorial, differs from that of C++ in several important respects.
Multiple inheritance may be used freely, without concern for
object slicing, erroneous down-casting or a
whole host of other gotchas familiar to C++ programmers. Methods are
separate from class declarations, allowing a programmer to write new
polymorphic functions without editing the relevant base class. Methods
may also dispatch ploymorphically on more than one parameter, a
powerful technique known as multiple dispatch.
All of these features will be explained in greater detail later on.

Garbage Collection

Languages with garbage collection have no
need of a free or delete
operator, because unused heap memory gets reclaimed automatically by
the language runtime. This reduces the complexity of source code,
eliminates the need of keeping reference counts for shared objects,
and prevents most memory allocation bugs and all memory leaks. There are several online platforms that offer online courses for you to try. Over the years, garbage collection has gained a reputation for
inefficiency. A large, object-oriented LISP program performed
terribly compared to hand coded, micro-optimized assembly, and a good
portion of the blame was placed on garbage collection.

Times have changed, however. Garbage collection technology has
improved. Processors speed has increased enormously. Most importantly,
however, the standard practice of the industry has changed, and large
commerical software is now built in C++.

No good benchmarks exist for the relative performance of large
C++ systems (greater than 15 thousand lines of code or so), and
similar systems designed from the ground up to use
garbage collection. The benchmarks which do exist typically test the
performance of relatively small pieces of code—small enough
that one programmer can optimize the overall usage of memory—or
have compared a good system without garbage collection to a direct
reimplementation of that system using a garbage collector. Overall,
no one seems to know just how fast GC is, relative to a typical large
C++ program. It is known, however, that good
GC code uses different designs than non-GC code, and often spends less
time needlessly copying data.

Why Not Dylan?

Dylan’s greatest weakness is the lack of commercial-quality
compilers, especially on the Macintosh. Apple has refused to comment
on the future of their technology release, and functional Objects is
targeting the Windows and Linux markets with their compiler. The

Gwydion Project
‘s Dylan implementation will support multiple UNIXs, Windows,
and Macintosh when complete.

Even when good Dylan environments become available, experience
suggests that Dylan applications will use more RAM than programs
written in traditional languages.

Chapter 2. Expressions & Variables

Dylan identifiers may contain a greater variety of characters
than those of C or Pascal. Specifically, variable names may contain all
alphanumeric characters, plus the symbols ! & * < = >
| ^ $ % @ _ - + ~ ? /
. Identifiers may not begin with the
symbols - + ~ ? /, although identifiers may begin
with numbers, provided they contain at least two alphabetic characters
in a row. As in Pascal, variable names are not case sensitive.
Need

Dylan Reference Manual

footnote here.

This means that (a - b) subtracts one variable
from another, whereas (a-b) simply returns the value
of the hyphenated variable named a-b. Because of this,
infix operators, such as addition, subtraction and equality, must be
surrounded by whitespace.

As in C++, Dylan infix operators may also be refered to as
functions. In C++, (a + b) could also be written
as operator+(a, b). In Dylan, the same expression
could be written \+(a, b). In both languages,
programmers can use this flexibility to define operators for custom
numeric classes.

Naming Conventions

Dylan uses the extra characters permitted in variable names to
support a number of standard naming conventions, as shown in
Table 2.1, “Naming Conventions”.

Table 2.1. Naming Conventions

<string> a class
insert! mutative function (modifies arguments destructively)
empty? predicate function (tests one or more arguments and
returns either true or false)
write-line a two word name
$name constant
*name* module-level variable

True and False

Dylan represents true as #t and false as
#f. When evaluated in a Boolean context, all values
other than #f return true. Thus, the number zero
—and other common “false” values—evaluate as
true in Dylan.

The Nature of Variables

Dylan variables differ from those found in C and Pascal. Instead
of holding their values, Dylan variables
refer to them. Conceptually, they resemble a
cross between pointers and C++ references. Like references, Dylan
variables may be evaluated without any indirection. Like pointers,
they may be set to point to new objects whenever the programmer
desires.

Furthermore, there’s only one of any given numeric value in a
Dylan program, at least from the programmer’s point of view. All
variables which refer to the integer 2—or, in Dylan-speak, are
bound to the integer 2—point to the
exact same thing.

let x = 2; // creates x and binds it to 2
x := 3;    // rebinds x to the value 3
let y = x; // creates y, and binds it to
           // whatever x is bound to

If two variables are bound to one object with internal
structure, the results may suprise C and Pascal programmers.

let car1 = make(<car>); // bind car1 to a
                              // new car object
car1.odometer := 10000;       // set odometer
let car2 = car1;              // bind new name
car2.odometer := 0;           // reset odometer
car1.odometer;                // evaluates to 0!

As long as one or more variables refer to an object, it
continues to exist. However, as soon as the last reference either
goes out of scope or gets rebound, the object becomes
garbage
. Since there’s no way that the program could ever
refer to the object again, the garbage collector
feels free to reuse the memory which once held it.

Note that Dylan variables must be bound to a
particular value when they are declared. In the name of type safety
and implementation efficiency, every variable must refer to some
well-defined object.

Assignment, Equality and Identity

Dylan uses all three of the “equals” operators
found in C and Pascal, albeit in a different fashion. The Pascal
assignment operator, :=, rebinds Dylan variable
names to new values. The Pascal equality operator, =
, tests for equality in Dylan and also appears in some
language constructs such as let. (Two Dylan objects
are equal, generally, if they belong to the same class and have equal
substructure.)

The C equality operator, ==, acts as the
identity operator in Dylan. Two variables are
identical if and only if they are bound to the
exact same object. For example, the following three expressions mean
roughly the same thing:

(a == b)   // in Dylan
(&a == &b) // in C or C++
(@a = @b) // in Pascal

The following piece of source code demonstrates all three
operators in actual use.

let car1 = make(<car>);
let car2 = make(<car>);
let car3 = car2;

car2 = car3;	// #t
car1 = car2;	// ??? (see below)
car2 == car3;	// #t
car1 == car2;	// #f

car2 := car1;	// rebind
car1 == car2;	// #t

let x = 2;
let y = 2;

x = y;			// #t
x == y;			// #t (only one 2!)

Two of the examples merit further explanation. First, we don’t
know whether car1 = car2, because we don’t know if
make creates each car with the same serial number, driver and other
information as previous cars. If and only if none of those values
differ, then car1 equals car2.
Second, x == y because every variable bound to a
given number refers to the exact same instance of that number, at least
from the programmer’s perspective. (The compiler will normally do
something more useful and efficient when generating the actual machine
code.) Strings behave in a fashion different from numbers—
instances of strings are stored separately, and two equal strings are
not necessarily the same string.

parallel Values

It’s possible to bind more than one variable at a time in Dylan.
For example, a single let statement could bind
x to 2, y to 3 and z
to 4.

let (x, y, z) = values (2, 3, 4);

In Perl, the equivalent statement would assign a vector of
values to a vector of variables. In Dylan, no actual vectors or lists
are used. All three values are assigned directly, using some
implementation-dependant mechanism.

Type Declarations

Dylan variables may have explicit types. This allows the
compiler to generate better code and to catch type-mismatch errors at
compile time. To take advantage of this feature, use the ::
operator:

let x :: <integer> = 2;
let vehicle :: <vehicle> = make(<car>);
let y :: <number> = 3; // any numeric class
let z :: <integer> = vehicle; // error!

As seen in the example, a variable may be bound to values of its
declared type or to values of subclasses of its declared type. Type
mismatch errors should be caught at compile time. In general, the
compiler may infer the types of variables at when generating machine
code. If a local variable never gets rebound to anything other than an
integer, for example, the compiler can rely on this fact to optimize
the resulting code.

Module Variables and Constants

Dylan supports module-level variables,
which serve roughly the same purpose as C’s global variables. Although
the let function may only be used within
methods
(Dylan-speak for regular functions), the forms
define variable and define constant
may be used at the top level.

define variable *x* :: <integer> = 3;
define variable *y* = 4;
define constant $hi = "Hi!";

Note that there’s not much point in declaring types for
constants. Any remotely decent compiler will be able to figure that
information out on its own.

Chapter 3. Methods & Generic functions

Dylan methods correspond roughly to the
functions found in C and Pascal. They take zero or more named parameters,
but also return zero or more named return values. A minimal Dylan method
might look like the following:

define method hello-world()
  puts("Hello, world!");
end;

This method has no parameters and an unspecified return value. It
could return any number of values of any type. In order to make the
above code more clear, the function could be rewritten as follows:

define method hello-world() => ();
  puts("Hello, world!");
end method;

There have been two changes. The function now officially returns
no value whatsoever. Also note that end has been
replaced by end method which could in turn be
rewritten as end method hello-world. In general,
Dylan permits all the obvious combinations of keywords and labels to
follow an end statement.

parameters & parameter Lists

Dylan methods declare parameters in fashion similar to that of
conventional languages, except for the fact that parameters may
optionally be untyped. Both of the following methods are legal:

define method foo(x :: <integer>, y) end;
define method bar(m, s :: <string>) end;

Both foo and bar have
one typed and one untyped parameter, but neither has a well-defined
return value (or actually does anything). As in C, each typed parameter
must have its own type declaration; there’s no syntax for saying
the last three parameters were all integers”.

Functions with variable numbers of parameters include the
#rest keyword at the end of their parameter lists.
Thus, the declaration for C’s printf function
would appear something like the following in Dylan:

define method printf(format-string :: <string>, #rest arguments) => ();
  // Print the format string, extracting
  // one at a time from "arguments". Note
  // that Dylan actually allows us to
  // verify the types of variables,
  // preventing those nasty printf errors,
  // such as using %d instead of %ld.
  // ...
end method printf;

For an actual implementation of a lightweight printf
function, see Appendix A.

Note that Dylan makes no provision for passing variables by
reference in the Pascal sense, or for passing pointers to variables.
parameter names are simply bound to whatever values are passed, and may
be rebound like regular variables. This means that there’s no way to
write a swap function in Dylan (except by using
macros). However, the following function works just fine, because it
modifies the internal state of another
object:

define method sell(car :: <car>, new-owner :: <string>) => ();
  if (credit-check(new-owner))
    car.owner = new-owner;
  else
    error("Bad credit!");
  end;
end;

If this sounds unclear, reread the chapter on variables and expressions.

Return Values

Because Dylan methods can’t have normal “output
parameters in their parameter lists, they’re allowed considerably
more flexibility when it comes to return values. Methods may return
more than one value. As with parameters, these values may be typed or
untyped. Interestingly enough, all return values must
be named.

A Dylan method—or any other control construct—returns
the value of the last expression in its body.

define method foo() => sample :: <string>;
  "Sample string.";		// return string
end;

define method bar() => my-untyped-value;
  if (weekend-day?(today()))
    "Let's party!";	// return string
  else
    make(<excuse>);	// return object
  end if;
end method;

define method moby( )
  =>	sample :: <string>, my-untyped-value;
  values( foo(), bar() ); // return both!
end;

define method baz( ) => ( );
  let (x,y) = moby( );		// assign both
end;

Bare Methods

Nameless methods may be declared inline. Such bare
methods
are typically used as parameters to other methods.
For example, the following code fragment squares each element of a list
using the built in map function and a bare
method:

define method square-list(in :: <list>)
  => out :: <list>
  map(method(x) x * x end, in);
end;

The map function takes each element of
the list in and applies the anonymous method. It
then builds a new list using the resulting values and returns it.
The method square-list might be invoked as
follows:Must distinguish return values from code.

square-list( #(1,2,3,4) );
=> #(1,4,9,16)

Local Methods

Local methods resemble bare methods but have names. They are
declared within other methods, often as private utility routines. Local
methods are typically used in a fashion similar to Pascal’s local
functions.

define method sum-squares(in :: <list>) => sum-of-element-squares :: <integer>;
  local method square( x )
          x * x;
        end,
        method sum(list :: <list>)
          reduce1(\+, list);
        end;
  sum(map(square, in));
end;

Local methods can actually outlive the invocation of the
function which created them. parameters of the parent function remain
bound in a local method, allowing some interesting techniques:

define method build-put(string :: <string>) => <function>;
  local method string-putter()
          puts(string);
        end;
  string-putter; // return local method
end;

define method print-hello() => ();
  let f = build-put("Hello!");
  f();				// print "Hello1"
end;

Local functions which contain bound variables in the above
fashion are known as closures.

Generic functions

A generic function represents zero or more
similar methods. Every method created by means of define
method
is automatically contained
within the generic function of the same name. For example, a
programmer could define three methods named display
, each of which acted on a different data type:

define method display(i :: <integer>)
  do-display-integer(i);
end;

define method display(s :: <string>)
  do-display-string(s);
end;

define method display(f :: <float>)
  do-display-float(f);
end;

When a program calls display, Dylan examines
all three methods. Depending on the number and type of arguments to
display, Dylan invokes one of the above methods.
If no methods match the actual parameters, an error occurs.

In C++, this process occurs only at compile time. (It’s called
operator overloading.) In Dylan, calls to display
may be resolved either at compile time or while the program is actually
executing. This makes it possible to define methods like:

define method display(c :: <collection>)
  for (item in c)
    display(item); // runtime dispatch
  end;
end;

This method extracts objects of unknown type from a collection,
and attempts to invoke the generic function display
on each of them. Since there’s no way for the compiler
to know what type of objects the collection actually contains, it
must generate code to identify and invoke the proper method at
runtime. If no applicable method can be found, the Dylan runtime
environment throws an exception.

Generic functions may also be declared explicity, allowing the
programmer to exercise control over what sort of methods get added.
For example, the following declaration limits all display
methods to single parameter and no return value:

define generic display(thing :: <object>) => ()

Generic functions are explained in greater detail in the chapter on
multiple dispatch.

Keyword Arguments

Functions may accept keyword arguments,
extra parameters which are identified by a label rather than by their
postion in the argument list. Keyword arguments are often used in a
fashion similar to default parameter values
in C++. For example, the following hypothetical method might print
records to an output device:

define method print-records(records :: <collection>,
  #key init-codes = "", lines-per-page = 66) => ();

  send-init-codes(init-codes);
  // ...print the records
end method;

This method could be invoked in one of several ways. The first
specifies no keyword arguments, and the latter two specify some
combination of them. Note that order of keyword arguments doesn’t
matter.

print-records(recs);
print-records(recs, lines-per-page: 65);
print-records(recs, lines-per-page: 120, init-codes: "***42\n");

Programmers have quite a bit of flexibility in specifying
keyword arguments. They may optionally omit the default value for a
keyword (in which case #f is used). Default value
specifiers may actually be function calls themselves, and may rely on
regular parameters already being in scope. Variable names may be
different from keyword names, a handy tool for preventing name
conflicts.

For more information on keyword arguments, especially their use
with generic functions,
see the

Dylan Reference Manual

.

Chapter 4. Objects

The features of Dylan’s object system don’t map directly onto the
features found in C++. Dylan handles access control using
modules
, not private declarations within
individual objects. Standard Dylan has no destructors, but instead relies
upon the garbage collector to recover memory and on exception handling
blocks to recover other resources. Dylan objects don’t even have real
member functions.

Despite these oddities, Dylan’s object system is at least as
powerful as that of C++. Multiple inheritance works smoothly,
constructors are rarely needed and there’s no such thing as object
slicing. Alternate constructs replace the missing C++ features. Quick
and dirty classes can be turned into clean classes with little editing
of existing code.

Before starting, temporarily set aside any low-level expertise in
C++ or Object Pascal. Dylan differs enough that such knowledge can
actually interfere with the initial learning process.

Built-In Classes

Dylan has a large variety of built-in classes. Several of these
represent primitive data types, such as <integer>
and <character>. A few represent
actual language-level entities, such as <class>
and <function>. Most of the others
implement collection classes, similar to those found in C++‘s
Standard Template Library. A few of the most important classes are
shown in Figure 4.1, “Several Standard Dylan Classes”.

Figure 4.1. Several Standard Dylan Classes

Several Standard Dylan Classes

The built-in collection classes include a number of common data
structures. Arrays, tables, vectors, ranges and deques should be
provided by all Dylan implementations. The language specification
also standardizes strings and byte-strings, certainly a welcome
convenience.

Not all the built-in classes may be subclassed. This allows the
compiler to heavily optimize code dealing with basic numeric types
and certain common collections. The programmer may also mark classes
as sealed, restricting how and where they may be
subclassed. See Chapter 6, Modules & Libraries for details.

Slots

Objects have slots, which resemble the data
members found in most other object-oriented languages. Like
variables, slots are bound to values; they don’t actually contain
their data. A simple Dylan class shows how slots are declared:

define class <vehicle> (<object>)
  slot serial-number;
  slot owner;
end;

The above code would quick and convenient to write while building
a prototype, but it could be improved. The slots have no types, and
worse, they have no initial values. (That’s no easy achievement in
Dylan, to create an uninitialized variable!) The following snippet
fixes both problems:

define class <vehicle> (<object>)
  slot serial-number :: <integer>,
    required-init-keyword: sn:;
  slot owner :: <string>,
    init-keyword: owner:, // optional
    init-value: "Northern Motors";
end class <vehicle>;

The type declarations work just like type declarations anywhere
else in Dylan; they limit a binding to objects of a given class or of
one of its subclasses, and they let the compiler optimize. The new
keywords describe how the slots get their initial values. (The keyword
init-function may also be used; it must be followed
by a function with no arguments and the appropriate return type.)

To create a vehicle object using the new class declaration, a
programmer could write one of the following:

make(<vehicle>, sn: 1000000)
make(<vehicle>, sn: 2000000, owner: "Sal")

In the first example, make returns a vehicle
with the specified serial number and the default owner. In the second
example, make sets both slots using the keyword
arguments.

Only one of required-init-keyword,
init-value
and init-function may be
specified. However, init-keyword may be paired with
either of the latter two if desired. More than one slot may be
initialized by a given keyword.

Dylan also provides for the equivalent of C++ static
members, plus several other useful allocation schemes. See
the

Dylan Reference Manual

for the full specifications.

Getters and Setters

An object’s slots are accessed using to functions: a getter and
a setter. By default, the getter function has the same name as the
slot, and the setter function appends “-setter
”. These functions may be invoked as follows:

owner(sample-vehicle);	// returns owner
owner-setter(sample-vehicle, "Faisal");

Dylan also provides some convenient “syntactic sugar
for these two functions. They may also be written as:

sample-vehicle.owner;		// returns owner
sample-vehicle.owner := "Faisal";

Generic functions and Objects

Generic functions, introduced in Methods and Generic functions
, provide the equivalent of C++ and Object Pascal member
functions. In the simplest case, just declare a generic function
which dispatches on the first parameter.

define generic tax(v :: <vehicle>)
  => tax-in-dollars :: <float>;

define method tax(v :: <vehicle>)
  => tax-in-dollars :: <float>;
  100.00;
end;

//=== Two new subclasses of vehicle

define class <car> (<vehicle>)
end;

define class <truck> (<vehicle>)
  slot capacity,
    required-init-keyword: tons:;
end;

//=== Two new "tax" methods

define method tax( c :: <car> )
  => tax-in-dollars :: <float>;
  50.00;
end method;

define method tax( t :: <truck> )
  => tax-in-dollars :: <float>;
  // standard vehicle tax plus $10/ton
  next-method( ) + t.capacity * 10.00;
end method;

The function tax could be invoked as
tax(v) or v.tax, because it
only has one argument. Generic functions with two or more arguments
must be invoked in the usual Dylan fashion; no syntactic sugar exists
to make them look like C++ member functions.

The version of tax for <truck> objects
calls a special function named next-method. This
function invokes the next most specific method of a generic function;
in this case, the method for <vehicle>
objects. Parameters to the current method get passed along
automatically.

Technically, next-method is a special
parameter to a method, and may be passed explicitly using
#next
. mindy, a popular but incomplete bytecode compiler
written as part of the
Gwydion Project
, currently requires the use of
#next.

define method tax(t :: <truck>, #next next-method)
  => tax-in-dollars :: <float>;
  // standard vehicle tax plus $10/ton
  next-method() + t.capacity * 10.00;
end method;

Dylan’s separation of classes and generic functions provides some
interesting design ideas. Classes no longer need to “contain
” their member functions; it’s possible to write a new generic
function without touching the class definition. For example, a module
handling traffic simulations and one handling municipal taxes could
each have many generic functions involving vehicles, but both could
use the same vehicle class.

Slots in Dylan may also be replaced by programmer-defined accessor
functions, all without modifying existing clients of the class. The


Dylan Reference Manual

describes numerous ways to accomplish the change; several should
be apparent from the preceding discussion. This flexibility frees
programmers from creating functions like GetOwnerName
and SetOwnerName, not to mention the
corresponding private member variables and constructor code.

For even more creative uses of generic functions and the Dylan
object model, see the chapter on
Multiple Dispatch
.

Initializers

The make function handles much of the
drudgery of object construction. It processes keywords and initializes
slots. Programmers may, however, customize this process by adding
methods to the generic function initialize. For
example, if vehicle serial numbers must be at least seven digits:

define method initialize(v :: <vehicle>, #all-keys) // accepts all keywords
  next-method( );
  if (v.serial-number < 1000000)
    error("Bad serial number!");
  end if;
end method;

Initialize methods get called after regular
slot initialization. They typically perform error checking or calculate
values for unusual slots. Initialize methods must accept all keywords
using #all-keys.

It’s possible to access the values of slot keywords from
initialize methods, and even to specify additional
keywords in the class declaration. See the

Dylan Reference Manual

for further details.

Abstract Classes and Overriding Make

Abstract classes define the interface, not the implementation,
of an object. There are no direct instances of an abstract class.
Concrete classes actually implement their interfaces. Every abstract
class will typically have one or more concrete subclasses. For example,
if plain vanilla vehicles shouldn’t exist, <vehicle>
could be defined as follows:

define abstract class <vehicle> (<object>)
  // ...as before
end;

The above modification prevents the creation of direct instances
of <vehicle>. At the moment, calling
make on this class would result in an error.
However, a programmer could add a method to make which allowed the
intelligent creation of vehicles based on some criteria, thus making
<vehicle> an instantiable abstract
class
:

define method make(class == <vehicle>,
  #rest keys, #key big? (#f), #all-keys)
  => <vehicle>;

  if ( big? )
    make( <truck>, keys, tons: 2 );
  else
    make( <car>, keys );
  end;
end;

A number of new features appear in the parameter list. The
expression “class == <vehicle>
specifies a singleton, one particular object
of a class which gets treated as a special case. Singletons are
discussed in the chapter on
Multiple Dispatch
. The use of #rest,
#key and #all-keys in the same
parameter list accepts any and all keywords, binds one of them to
big? and places all of them into the variable
keys. The new make method could be invoked in
any of the following fashions:

let x = 1000000;
make(<vehicle>, sn: x, big?: #f); =>car
make(<vehicle>, sn: x, big?: #t); =>truck
make(<vehicle>, sn: x);           =>car

Methods added to make don’t actually need
to create new objects. Dylan officially allows them to return existing
objects. This can be used to manage lightweight shared objects, such
as the “flyweights” described by Gamma, et al., in

Design Patterns
.

Chapter 5. Multiple Dispatch

Multiple dispatch is one of the most powerful
and elegant features of Dylan. As explained in the section on generic functions and objects,
Dylan methods are declared separately from the classes upon which they
act. Polymorphism, the specialization of methods
for use with particular classes, can be implemented by declaring several
methods with different parameters and attaching them to one generic
function:

define generic inspect-vehicle(v :: <vehicle>, i :: <inspector>) => ();

define method inspect-vehicle(v :: <vehicle>, i :: <inspector>) => ();
  look-for-rust(v);
end;

define method inspect-vehicle(car :: <car>, i :: <inspector>) => ();
  next-method(); // perform vehicle inspection
  check-seat-belts(car);
end;

define method inspect-vehicle(truck :: <truck>, i :: <inspector>) => ();
  next-method(); // perform vehicle inspection
  check-cargo-attachments(truck);
end;

However, different types of vehicle inspectors may have different
policies. A state inspector, in addition to the usual procedures, will
also typically check a car’s insurance policy. To implement this, add
another method to the generic function inspect—vehicle
:

define method inspect-vehicle(car :: <car>, i :: <state-inspector>) => ();
  next-method(); // perform car inspection
  check-insurance(car);
end;

let inspector = make(<state-inspector>);
let car = make(<car>);
inspect-vehicle(car, inspector);

Calling the generic function inspect-vehicle
with these arguments performs three separate tasks:
look-for-rust
, check-seat-belts and
check-insurance. The most specific method on
inspect-vehicle—the one for the classes
<car> and <state-inspector>
—is invoked first and calls next-method
to invoke the less-specific methods in turn.

For an exact definition of “specific”, see the

Dylan Reference Manual

.

Dispatching on Specific Objects

Dylan also allows functions to dispatch on specific objects. For
example, state inspectors might pass the governor’s car without
actually looking at it. Dylan expresses this situation using
singletons, objects which are treated as
though they were in a class of their own. For example:

define constant $governors-car = make(<car>);

define method inspect-vehicle(car == $governors-car,
  i :: <state-inspector>) => ();
  wave-through(car);
end;

(In this example, none of the usual inspection methods will be
invoked since the above code neglects to call next-method
.)

Chapter 6. Modules & Libraries

Modules and libraries provide the structure of a Dylan program. Modules
represent namespaces and control access to objects and functions.
Libraries contain modules, and act as units of compilation in a
finished Dylan program.

Simple Modules

Modules import the symbols of other modules and export their
own. The dependencies between modules must form a directed, acyclic
graph. Two modules may not use each other, and no circular
dependencies may exist.

Modules only export variables. Since the names of classes and
generic functions are actually stored in variables, this represents
no hardship. A sample module containing the vehicle classes from
earlier chapters might resemble:

define module Vehicles
  use Dylan;
  export
    <vehicle>,
      serial-number,
      owner, owner-setter,
      tax,
    <car>,
    <truck>,
      capacity;
end module;

Like all normal modules, this one uses the Dylan
module, which contains all of the standard built-in
functions and classes. In turn, the Vehicles
module exports all three of the vehicle classes, the generic function
tax, several getter functions and a single
setter function.

To control access to a slot, export some combination of its
getter and setter functions. To make a slot public, export both. To
make it read-only, export just the getter function. To make it
private, export neither. In the above example, the slot
serial-number is read-only, while the slot
owner is public.

Note that when some module adds a method to a generic function,
the change affects all modules using that function. The new method
actually gets added into the variable representing
the generic function. Since the variable has been previously exported,
all clients can access the new value.

Import Options

Dylan allows very precise control over how symbols are imported
from other modules. For example, individual symbols may be imported
by name. They may be renamed, either one at a time, or by adding a
prefix to all a module’s symbols at once. Some or all of them may be
re-exported immediately. See the

Dylan Reference Manual

for specific examples.

Dylan’s import system has a number of advantages. Name conflicts
occur rarely. Programmers don’t need to define or maintain function
prototypes. There’s no explicit need for header files. Modules may
also provide different interfaces to the same objects—one module
exports a complete interface, which another module imports, redefines
and re-exports.

Libraries

Libraries contain modules. For example, the Dylan
library contains the Dylan module
described earlier, the Extensions module, and
possibly several other implementation-dependent modules. Note that
a library and a module may share a given name. Modules with the
same name may also appear in more than one library.

By default, a Dylan environment provides a library called
Dylan-User for the convenience of the programmer.
This is typically used for short, single library programs which
depend only on modules found in the Dylan library.

Additionally, every library contains an implicit module, also
known as Dylan-User, which imports all of the
modules found in the Dylan library. This may be
used for single module programs. Many Dylan environments, however,
use it to bootstrap new library definitions. The vehicle library,
for example, might be defined as follows in a Dylan-User
module:

define library Vehicles
  use Dylan;            // This is the library!
  export                // These are modules.
    Vehicles,           // (Defined above.)
    Traffic-Simulation,
    Crash-Testing,
    Inspection;         // (Hypothetical.)
end library Vehicles;

This library could in turn be imported by another library:

define library Vehicle-Application
  use Dylan;
  use My-GUI-Classes;
  use Vehicles;
end;

Libraries import other libraries and export modules, whereas
modules import other modules and export variables. In general, a
module may import any module found in its own library or exported
from a library imported by its own library. The following module, for
example, could belong to the Vehicle-Application
library.

define module Sample-Module
  // module name         source library
  use Dylan;          // Dylan
  use Extensions;     // Dylan
  use Menus;          // My-GUI-Classes
  use Vehicles;       // Vehicles
  use Inspection;     // Vehicles
end module;

A Sample Set of Libraries

Figure 6.1, “Dependencies Among the Standard Mindy Libraries” and Table 6.1, “Standard Mindy Libraries and Modules” show the libraries included
with mindy, a bytecode compiler produced as part of the

Gwydion Project
. Also shown are the dependencies between the libraries and
the major modules contained within each. For more information on
these libraries and their use, see the mindy documentation and
source code.

Figure 6.1. Dependencies Among the Standard Mindy Libraries

Dependencies Among the Standard Mindy Libraries

Table 6.1. Standard Mindy Libraries and Modules

Library Module Purpose
Dylan Dylan standard Dylan
Extensions Mindy extensions
System general functions
File-Descriptors UNIX-style file support
Threads threads and semaphores
Hash-Tables additional table support
Introspection program examination
Cheap-IO basic, unformatted I/O
Extern support for linking to C
Transcendental trig and other math
Collection-Extensions Heap standard heaps
Self-Organizing-List poor man’s hash table
Vector-Search search, replace support
Subseq slices of sequences
Sequence-Diff diff” for sequences
String-Extensions String-Conversions strings to/from integers
Character-Type similar to C’s “ctype.h
String-Hacking useful string functions
Substring-Search similar to Perl
Regular-Expressions similar to Perl
Streams Streams UNIX stream I/O
Standard-IO the 3 standard streams
Print PPrint Pretty-printing
Print General object output
Format Format Formatted output
Random Random Random distributions

The String-Extensions library, in particlar,
contains a number of useful functions. It provides many of Perl’s
popular text-manipulation features.

Sealing

Classes and generic functions may be sealed
using a number of Dylan forms. This prevents code in other libraries
from subclassing objects or adding methods to generic functions, and
lets the compiler optimize more effectively. Both classes and generic
functions are sealed by default.

To allow code in other libraries to subclass a given class,
declare it as open:

define open class <sample> (<object>) end;

To allow other libraries to add methods to a generic function,
use a similar syntax:

define open generic sample-function( o :: <object> ) => ();

A third form, define inert domain, partially
seals a generic function, disallowing only some additions from outside
a library.

For more information on sealing, see the chapter
Controlling Dynamism” in the

Dylan Reference Manual

.

Chapter 7. Conditions

Table of Contents

Blocks

Dylan offers sophisticated exception handling, allowing programs
to recover smoothly from error conditions. Like C++, Dylan represents
errors with objects. Dylan also supports advisory warnings and
potentially correctable errors.

When something unusual happens, a program can signal
a condition. Handlers
specify how to react to various sorts of conditions.

Blocks

A block is a group of statements. As with
other control structures, it may return a value. A simple block
might appear as follows:

block ()
  1 + 1;
end; // returns 2

Blocks also support non-local exits. These allow a block to exit at
any time, optionally returning a value. In some ways, they are
similar to goto statements or the
POSIX
longjmp function. To use them,
specify a name in the parentheses following a block
statement. Dylan binds this name to an exit
function
which can be called from anywhere within the
block or the functions it calls. The following block returns either
"Weird!" or "All's well.",
depending on the color of the sky.

block (finished)
  if (sky-is-green())
    finished("Weird!");
  end;
  "All's well."
end block;

Many programs need to dispose of resources or perform other cleanup
work, regardless of how a block is exited. Blocks may contain
an optional cleanup clause, which doesn’t affect
the return value of the block and will always be executed.

let fd = open-input-file();
block (return)
  let (errorcode, data) = read-data(fd);
  if (errorcode)
    return(errorcode);
  end if;
  process-data(data);
cleanup
  close(fd);
end;