A Simple Library¶
In this chapter, we create a complete library that represents time and
position. The timespace
library provides the say
generic function
for all concrete classes, the mathematical function +
on certain kinds
of time, and the comparison functions <
and =
, which enable users
to call all other numerical comparisons, >
, >=
, <=
, and ~=
.
Our library consists of four files:
The LID file lists all the files in the
timespace
library.The library file defines the
timespace
library and thetimespace
module.The implementation file defines the classes, methods, and generic functions.
The test file creates instances, calls
say
on them, and adds time instances.
We provide the test file so that you can experiment with the library. Because the test code has a purpose different from that of the implementation code, we separate them into two files. Normally, a finished library would not contain both the implementation and the test code — the test code would be in a separate library. However, when you are starting to implement your program or library, it is convenient to put all the code in one library, as we do here.
We shall continue to develop the time and position library in Part 2. Intermediate Topics. The complete version is given in Four Complete Libraries.
The LID file¶
The LID file: timespace.lid
.
library: timespace
files: library
library-implementation
test
The library file¶
The library file: library.dylan
.
module: dylan-user
define library timespace
use dylan;
use format-out;
end library timespace;
define module timespace
use dylan;
use format-out;
end module timespace;
The implementation file¶
The implementation file: library-implementation.dylan
.
module: timespace
// The sixty-unit class
define abstract class <sixty-unit> (<object>)
slot total-seconds :: <integer>, init-keyword: total-seconds:;
end class <sixty-unit>;
// decode-total-seconds
define method decode-total-seconds
(sixty-unit :: <sixty-unit>)
=> (max-unit :: <integer>, minutes :: <integer>, seconds :: <integer>)
decode-total-seconds(abs(sixty-unit.total-seconds));
end method decode-total-seconds;
define method decode-total-seconds
(total-seconds :: <integer>)
=> (hours :: <integer>, minutes :: <integer>, seconds :: <integer>)
let (total-minutes, seconds) = truncate/(total-seconds, 60);
let (hours, minutes) = truncate/(total-minutes, 60);
values(hours, minutes, seconds);
end method decode-total-seconds;
// encode-total-seconds
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;
// The say generic function
// Given an object, print a description of the object
define generic say (any-object :: <object>) => ();
// The time classes and methods
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>;
// A relative time between -24:00 and +24:00
define class <time-offset> (<time>)
end class <time-offset>;
// Method for determining whether a time offset is in the past
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 (past?(time)) "minus" else "plus" end);
next-method();
end method say;
// Methods for adding times
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 \+;
// Methods for comparing times
define method \< (time1 :: <time-of-day>, time2 :: <time-of-day>)
=> (boolean :: <boolean>)
time1.total-seconds < time2.total-seconds;
end method \<;
define method \< (time1 :: <time-offset>, time2 :: <time-offset>)
=> (boolean :: <boolean>)
time1.total-seconds < time2.total-seconds;
end method \<;
define method \= (time1 :: <time-of-day>, time2 :: <time-of-day>)
=> (boolean :: <boolean>)
time1.total-seconds = time2.total-seconds;
end method \=;
define method \= (time1 :: <time-offset>, time2 :: <time-offset>)
=> (boolean :: <boolean>)
time1.total-seconds = time2.total-seconds;
end method \=;
// The angle classes and methods
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>;
// We need to show degrees for <relative-angle> but we do not need to
// show minutes and seconds, so we override the method on <angle>
define method say (angle :: <relative-angle>) => ()
format-out("%d degrees", decode-total-seconds(angle));
end method say;
define abstract class <directed-angle> (<angle>)
slot direction :: <string>, init-keyword: direction:;
end class <directed-angle>;
define method say (angle :: <directed-angle>) => ()
next-method();
format-out(" %s", angle.direction);
end method say;
// The latitude and longitude classes and methods
define class <latitude> (<directed-angle>)
end class <latitude>;
define method say (latitude :: <latitude>) => ()
next-method();
format-out(" latitude\n");
end method say;
define class <longitude> (<directed-angle>)
end class <longitude>;
define method say (longitude :: <longitude>) => ()
next-method();
format-out(" longitude\n");
end method say;
// The position classes and methods
define abstract class <position> (<object>)
end class <position>;
define class <absolute-position> (<position>)
slot latitude :: <latitude>, init-keyword: latitude:;
slot longitude :: <longitude>, 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>, init-keyword: distance:;
slot angle :: <angle>, init-keyword: angle:;
end class <relative-position>;
define method say (position :: <relative-position>) => ()
format-out("%d miles away at heading ", position.distance);
say(position.angle);
end method say;
The test file¶
The test file: test.dylan
.
module: timespace
format-out("Creating an instance of <absolute-position>:\n");
define variable *my-absolute-position*
= make(<absolute-position>,
latitude: make(<latitude>,
total-seconds: encode-total-seconds(42, 19, 34),
direction: "North"),
longitude: make(<longitude>,
total-seconds: encode-total-seconds(70, 56, 26),
direction: "West"));
say(*my-absolute-position*);
format-out("\n");
format-out("Creating an instance of <relative-position>:\n");
define variable *her-relative-position*
= make(<relative-position>,
distance: 30.0,
angle: make(<relative-angle>,
total-seconds: encode-total-seconds(90, 5, 0)));
say(*her-relative-position*);
format-out("\n");
format-out("Creating an instance of <time-offset> in *minus-2-hours*.\n");
define variable *minus-2-hours*
= make(<time-offset>, total-seconds: - encode-total-seconds (2, 0, 0));
format-out("Creating an instance of <time-offset> in *plus-15-20-45*.\n");
define variable *plus-15-20-45*
= make(<time-offset>, total-seconds: encode-total-seconds (15, 20, 45));
format-out("Creating an instance of <time-of-day> in *8-30-59*.\n");
define variable *8-30-59*
= make(<time-of-day>, total-seconds: encode-total-seconds (8, 30, 59));
format-out("Adding <time-offset> + <time-offset>: *minus-2-hours* + *plus-15-20-45*:\n");
decode-total-seconds(*minus-2-hours* + *plus-15-20-45*);
format-out("Adding <time-offset> + <time-of-day>: *minus-2-hours* + *8-30-59*:\n");
decode-total-seconds(*minus-2-hours* + *8-30-59*);
format-out("Adding <time-of-day> + <time-offset>: *8-30-59* + *minus-2-hours*:\n");
decode-total-seconds(*8-30-59* + *minus-2-hours*);
When we run the test file, we see the following output and values:
Creating an instance of <absolute-position>:
42 degrees 19 minutes 34 seconds North latitude
70 degrees 56 minutes 26 seconds West longitude
Creating an instance of <relative-position>:
30.000000 miles away at heading 90 degrees
Creating an instance of <time-offset> in *minus-2-hours*.
Creating an instance of <time-offset> in *plus-15-20-45*.
Creating an instance of <time-of-day> in *8-30-59*.
Adding <time-offset> + <time-offset>: *minus-2-hours* + *plus-15-20-45*:
13
20
45
Adding <time-offset> + <time-of-day>: *minus-2-hours* + *8-30-59*:
6
30
59
Adding <time-of-day> + <time-offset>: *8-30-59* + *minus-2-hours*:
6
30
59
Summary¶
In this chapter, we created the four files that constitute the
timespace
library.