Four Complete Libraries¶
In this chapter, we show all the files that the complete time,
angle, sixty-unit, and say libraries comprise.
The sixty-unit library¶
The sixty-unit library is an example of a shared substrate library.
Both the time and angle libraries use the sixty-unit library to
create more specialized classes that build on a common substrate.
The sixty-unit library comprises two Dylan interchange-format files: a
library file, containing the library and module definitions; and an
implementation file, containing a single source record, defining the
generic function that is the say protocol. For completeness, we also
show the LID file that describes the library and its component files.
The sixty-unit-library file¶
The sixty-unit-library file: sixty-unit-library.dylan.
Module: dylan-user
// Library definition
define library sixty-unit
// Interface module
export sixty-unit;
// Substrate library
use dylan;
end library sixty-unit;
// Interface module
define module sixty-unit
// Classes
create <sixty-unit>;
// Generics
create total-seconds, encode-total-seconds, decode-total-seconds;
end module sixty-unit;
// Implementation module
define module sixty-unit-implementation
// External interface
use sixty-unit;
// Substrate module
use dylan;
end module sixty-unit-implementation;
The sixty-unit implementation file¶
The sixty-unit implementation file: sixty-unit.dylan.
Module: sixty-unit-implementation
define open abstract class <sixty-unit> (<object>)
slot total-seconds :: <integer>, required-init-keyword: total-seconds:;
end class <sixty-unit>;
define method encode-total-seconds
(max-unit :: <integer>, minutes :: <integer>, seconds :: <integer>)
=> (total-seconds :: <integer>)
((max-unit * 60) + minutes) * 60 + seconds;
end method encode-total-seconds;
define method decode-total-seconds
(sixty-unit :: <sixty-unit>)
=> (max-unit :: <integer>, minutes :: <integer>, seconds :: <integer>)
decode-total-seconds(sixty-unit.total-seconds);
end method decode-total-seconds;
define method decode-total-seconds
(total-seconds :: <integer>)
=> (max-unit :: <integer>, minutes :: <integer>, seconds :: <integer>)
let (total-minutes, seconds) = truncate/(abs(total-seconds), 60);
let (max-unit, minutes) = truncate/(total-minutes, 60);
values(max-unit, minutes, seconds);
end method decode-total-seconds;
The sixty-unit LID file¶
The LID file: sixty-unit.lid.
library: sixty-unit
files: sixty-unit-library
sixty-unit
The say library¶
The say library is an example of a library that defines a shared
protocol. All our other libraries use the say library, so that they
can add to the say generic function methods that appropriately display
the objects of the classes that they define.
The say library comprises two Dylan interchange-format files: a
library file, containing the library and module definitions; and an
implementation file, containing a single source record, defining the
generic function that is the say protocol. For completeness, we also
show the LID file that describes the library and its component files.
The say-library file¶
The say-library file: say-library.dylan.
Module: dylan-user
// Library definition
define library say
// Interface modules
export say, say-implementor;
// Substrate libraries
use format-out;
use dylan;
end library say;
// Protocol interface
define module say
create say;
end module say;
// Implementor interface
define module say-implementor
use say, export: all;
use format-out, export: all;
end module say-implementor;
// Implementation module
define module say-implementation
use say;
use dylan;
end module say-implementation;
The say implementation file¶
The say implementation file: say.dylan.
Module: say-implementation
define open generic say (object :: <object>) => ();
The say LID file¶
The LID file: say.lid.
library: say
files: say-library
say
The time library¶
The time library is a client of the sixty-unit and say libraries,
and it will serve as a substrate library for the rest of our
application. Like the previous two libraries, it comprises a library
file and an implementation file; we also show the corresponding LID
file.
The time-library file¶
The time-library file: time-library.dylan.
Module: dylan-user
// Library definition
define library time
// Interface module
export time;
// Substrate libraries
use sixty-unit;
use say;
use dylan;
end library time;
// Interface module
define module time
// Classes
create <time>, <time-of-day>, <time-offset>;
// Types
create <nonnegative-integer>;
// Constants
create $midnight, $tomorrow;
// Shared protocol
use say, export: all;
use sixty-unit, import: { encode-total-seconds }, export: all;
end module time;
// Implementation module
define module time-implementation
// External interface
use time;
// Substrate modules
use sixty-unit;
use say-implementor;
use dylan;
end module time-implementation;
The time implementation file¶
The time implementation file: time.dylan.
Module: time-implementation
// Define nonnegative integers as integers that are >= zero
define constant <nonnegative-integer> = limited(<integer>, min: 0);
define abstract class <time> (<sixty-unit>)
end class <time>;
define method say (time :: <time>) => ()
let (hours, minutes) = decode-total-seconds(time);
format-out("%d:%s%d",
hours, if (minutes < 10) "0" else " " end, minutes);
end method say;
// A specific time of day from 00:00 (midnight) to before 24:00 (tomorrow)
define class <time-of-day> (<time>)
end class <time-of-day>;
define method total-seconds-setter
(total-seconds :: <integer>, time :: <time-of-day>)
=> (total-seconds :: <nonnegative-integer>)
if (total-seconds >= 0)
next-method();
else
error("%d cannot be negative", total-seconds);
end if;
end method total-seconds-setter;
define method initialize (time :: <time-of-day>, #key)
next-method();
if (time.total-seconds < 0)
error("%d cannot be negative", time.total-seconds);
end if;
end method initialize;
// A relative time between -24:00 and +24:00
define class <time-offset> (<time>)
end class <time-offset>;
define method past? (time :: <time-offset>) => (past? :: <boolean>)
time.total-seconds < 0;
end method past?;
define method say (time :: <time-offset>) => ()
format-out("%s ", if (time.past?) "minus" else "plus" end);
next-method();
end method say;
define method \+
(offset1 :: <time-offset>, offset2 :: <time-offset>)
=> (sum :: <time-offset>)
let sum = offset1.total-seconds + offset2.total-seconds;
make(<time-offset>, total-seconds: sum);
end method \+;
define method \+
(offset :: <time-offset>, time-of-day :: <time-of-day>)
=> (sum :: <time-of-day>)
make(<time-of-day>,
total-seconds: offset.total-seconds + time-of-day.total-seconds);
end method \+;
define method \+ (time-of-day :: <time-of-day>, offset :: <time-offset>)
=> (sum :: <time-of-day>)
offset + time-of-day;
end method \+;
define method \< (time1 :: <time-of-day>, time2 :: <time-of-day>)
time1.total-seconds < time2.total-seconds;
end method \<;
define method \< (time1 :: <time-offset>, time2 :: <time-offset>)
time1.total-seconds < time2.total-seconds;
end method \<;
define method \= (time1 :: <time-of-day>, time2 :: <time-of-day>)
time1.total-seconds = time2.total-seconds;
end method \=;
define method \= (time1 :: <time-offset>, time2 :: <time-offset>)
time1.total-seconds = time2.total-seconds;
end method \=;
// Two useful time constants
define constant $midnight
= make(<time-of-day>, total-seconds: encode-total-seconds(0, 0, 0));
define constant $tomorrow
= make(<time-of-day>,
total-seconds: encode-total-seconds(24, 0, 0));
The time LID file¶
The LID file: time.lid.
library: time
files: time-library
time
The angle library¶
The angle library is the second client of the sixty-unit substrate.
The angle library extends the say protocol to handle objects of the
classes that it defines, such as <latitude>, <longitude>, and
<absolute-position>. For the time being, we have included positions
with angles, as we do not foresee any benefit to breaking them out into
yet another library, at least for the current application. Nevertheless,
we have defined separate interface and implementation modules for
positions, and we have broken out the position source records into a
separate interchange file.
Like with the time library, the angle library file does not have to
specify the use of the format-out library. It will be transitively
included because it is exported by the say library. Similarly, clients
of the angle library do not need to know anything about the say and
sixty-unit libraries, since those libraries are imported and
re-exported to clients of angle.
Note that the position-implementation module uses the angle module —
it is an internal client of the angle module. This structure means
that we can easily break out positions as a separate library, should the
need arise.
Also note that we have used the angle interface module to enforce
access control on the internal-direction slot. It should be accessed
only through the direction and direction-setter methods, which
ensure that valid values are used for our <latitude> and <longitude>
classes. Because only the approved generic functions are created in the
interface module, only they will be accessible to clients of the angle
library. The internal-direction slot is truly internal to the angle
library — no client library can even determine its existence.
The angle-library file¶
The angle-library file: angle-library.dylan.
Module: dylan-user
// Library definition
define library angle
// Interface module
export angle, position;
// Substrate libraries
use sixty-unit;
use say;
use dylan;
end library angle;
// Interface module
define module angle
// Classes
create <angle>, <relative-angle>, <directed-angle>, <latitude>, <longitude>;
// Generics
create direction, direction-setter;
// Shared protocol
use say, export: all;
use sixty-unit, import: { encode-total-seconds }, export: all;
end module angle;
// Interface module
define module position
// Classes
create <position>, <absolute-position>, <relative-position>;
// Generics
create distance, angle, latitude, longitude;
// Shared protocol
use say, export: all;
end module position;
// Implementation module
define module angle-implementation
// External interface
use angle;
// Substrate modules
use sixty-unit;
use say-implementor;
use dylan;
end module angle-implementation;
// Implementation module
define module position-implementation
// External interface
use position;
// Substrate modules
use angle;
use say-implementor;
use dylan;
end module position-implementation;
The angle implementation file¶
The angle implementation file is simply a collection of the source
records that we developed earlier for creating and saying angles,
latitudes, and longitudes.
The angle implementation file: angle.dylan.
Module: angle-implementation
define abstract class <angle> (<sixty-unit>)
end class <angle>;
define method say (angle :: <angle>) => ()
let (degrees, minutes, seconds) = decode-total-seconds(angle);
format-out("%d degrees %d minutes %d seconds",
degrees, minutes, seconds);
end method say;
define class <relative-angle> (<angle>)
end class <relative-angle>;
define method say (angle :: <relative-angle>) => ()
format-out(" %d degrees", decode-total-seconds(angle));
end method say;
define abstract class <directed-angle> (<angle>)
virtual slot direction :: <symbol>;
slot internal-direction :: <symbol>;
keyword direction:;
end class <directed-angle>;
define method initialize (angle :: <directed-angle>, #key direction: dir)
next-method();
angle.direction := dir;
end method initialize;
define method direction (angle :: <directed-angle>) => (dir :: <symbol>)
angle.internal-direction;
end method direction;
define method direction-setter
(dir :: <symbol>, angle :: <directed-angle>) => (new-dir :: <symbol>)
angle.internal-direction := dir;
end method direction-setter;
define method say (angle :: <directed-angle>) => ()
next-method();
format-out(" %s", angle.direction);
end method say;
define class <latitude> (<directed-angle>)
end class <latitude>;
define method say (latitude :: <latitude>) => ()
next-method();
format-out(" latitude\n");
end method say;
define method direction-setter
(dir :: <symbol>, latitude :: <latitude>) => (new-dir :: <symbol>)
if (dir == #"north" | dir == #"south")
next-method();
else
error("%= is not north or south", dir);
end if;
end method direction-setter;
define class <longitude> (<directed-angle>)
end class <longitude>;
define method say (longitude :: <longitude>) => ()
next-method();
format-out(" longitude\n");
end method say;
define method direction-setter
(dir :: <symbol>, longitude :: <longitude>) => (new-dir :: <symbol>)
if (dir == #"east" | dir == #"west")
next-method();
else
error("%= is not east or west", dir);
end if;
end method direction-setter;
The position implementation file¶
The position implementation file is simply a collection of the source
records that we developed earlier for creating and saying absolute and
relative positions.
The position implementation file: position.dylan.
Module: position-implementation
define abstract class <position> (<object>)
end class <position>;
define class <absolute-position> (<position>)
slot latitude :: <latitude>, required-init-keyword: latitude:;
slot longitude :: <longitude>, required-init-keyword: longitude:;
end class <absolute-position>;
define method say (position :: <absolute-position>) => ()
say(position.latitude);
say(position.longitude);
end method say;
define class <relative-position> (<position>)
// Distance is in miles
slot distance :: <single-float>, required-init-keyword: distance:;
// Angle is in degrees
slot angle :: <angle>, required-init-keyword: angle:;
end class <relative-position>;
define method say (position :: <relative-position>) => ()
format-out("%s miles away at heading ", position.distance);
say(position.angle);
end method say;
The angle LID file¶
Because we have chosen to put the source records for positions in a
separate interchange file, the LID file lists three Dylan files that
make up the angle library.
The LID file: angle.lid.
library: angle
files: angle-library
angle
position
Summary¶
The structure of protocol and substrate libraries that we have created is perhaps overly complex for the simple functionality that we have implemented here. However, the libraries illustrate the power of the Dylan module and library system to modularize large projects into easily manageable sub-projects, and to control the interfaces among those projects.