7.4 Absolute position

The <absolute-position> class represents latitude and longitude. One way to represent latitude and longitude is with degrees, minutes, seconds, and a direction. We can use the approach of combining degrees, minutes, and seconds into a total-seconds slot as we did for <time>. We can also define a class that represents total seconds and a direction, and call it <directed-angle>:

define abstract class <directed-angle> (<object>)
  slot total-seconds :: <integer>, init-keyword: total-seconds:;
  slot direction :: <string>, init-keyword: direction:;
end class <directed-angle>;

We use the <directed-angle> class in the definition of <absolute-position>:

define class <absolute-position> (<position>)
  slot latitude :: <directed-angle>, init-keyword: latitude:;
  slot longitude :: <directed-angle>, init-keyword: longitude:;
end class <absolute-position>;

Modularity note: The <directed-angle> class represents the characteristics that latitude and longitude have in common.

Comparison to C: If you are familiar with a language that uses explicit pointers, such as C, you may be confused by Dylan's object model. Although there is no pointer-to operation in Dylan, there are pointers in the implementation. If you are trying to imagine how Dylan objects are implemented, think in terms of always manipulating a pointer to the object — a Dylan variable (or slot) stores a pointer to an object, rather than a copy of the object's slots. Similarly, assignment, argument passing, and identity comparison are in terms of pointers to objects. See Appendix B, Dylan Object Model for C and C++ Programmers.

Comparison to Java: Java recognizes that pointers make it extremely difficult to enforce safety and for a compiler to reason about a program for optimization. Java supports an object model similar to that of Dylan, where pointers are used in the implementation of objects, but are not visible to Java programs.

We could define the say method as follows:

define method say (position :: <absolute-position>) => ()
  format-out("%d degrees %d minutes %d seconds %s latitude\n",
             decode-total-seconds(position.latitude));
  format-out("%d degrees %d minutes %d seconds %s longitude\n",
             decode-total-seconds(position.longitude));
end method say;

The preceding method depends on decode-total-seconds having a method that is applicable to <directed-angle> (the type of the objects returned by position.latitude and position.longtude). We define such a method in Section 7.6.

Modularity note: The preceding say method does not take advantage of the similarity between latitude and longitude. One clue that there is a modularity problem is that the two calls to format-out are nearly identical.

The say method on <absolute-position> should not call format-out directly on the two instances of <directed-angle> stored in the latitude and longitude slots. Instead, we can define a say method on <directed-angle>, and can call it in the method on <absolute-position>:

define method say (angle :: <directed-angle>) => ()
  let(degrees, minutes, seconds) = decode-total-seconds(angle);
  format-out("%d degrees %d minutes %d seconds %s",
             degrees, minutes, seconds, angle.direction);
end method say; 
define method say (position :: <absolute-position>) => ()
  say(position.latitude);
  format-out(" latitude\n");
  say(position.longitude);
  format-out(" longitude\n");
end method say;

Modularity note: Our modularity is improved, now that the <directed-angle> class is responsible for describing its instances. This division of labor reduces duplication of code.

There is still a problem with this approach, because the say method on <absolute-position> must print "latitude" and "longitude" after calling say on the directed angles stored in its two slots. The modularity is still flawed, because the method on <absolute-position> acts on the knowledge that the method on <directed-angle> does not print "latitude" or "longitude."

We defined the <directed-angle> class to represent what latitude and longitude have in common. It is useful to recognize that latitude and longitude have differences as well as similarities. We represented latitude and longitude by the names of slots in <absolute-position>, and their implementations as instances of <directed-angle>. We can elevate the visibility of latitude and longitude by providing classes that represent each of them:

define class <latitude> (<directed-angle>)
end class <latitude>;
define class <longitude> (<directed-angle>)
end class <longitude>;

We redefine <absolute-position> to use <latitude> and <longitude>:

define class <absolute-position> (<position>)
  slot latitude :: <latitude>, init-keyword: latitude:;
  slot longitude :: <longitude>, init-keyword: longitude:;
end class <absolute-position>;
Figure 7.1 Inheritance relationships among the position and angle classes. Abstract classes are shown in oblique typewriter font.

Figure 7.1

Figure 7.1 shows the inheritance relationships among the position and angle classes.

We define these new say methods:

define method say (latitude :: <latitude>) => ()
  next-method();
  format-out(" latitude\n");
end method say;
define method say (longitude :: <longitude>) => ()
  next-method();
  format-out(" longitude\n");
end method say;

The calls to next-method in the methods on <latitude> and <longitude> will call the method on <directed-angle>, shown on page 87.

We redefine the say method on <absolute-position>:

define method say (position :: <absolute-position>) => ()
  say(position.latitude);
  say(position.longitude);
end method say;

Modularity note: The approach of defining the classes <latitude> and <longitude> provides the following benefits:

  • Each class is responsible for describing its instances. Each method depends on say working for all the classes. No method on one class must understand the details of a method on another class.

  • We guard against any attempt to store a latitude in a slot designated for a longitude, and vice versa. This type checking will be useful when we introduce more differences between the classes. For example, the direction of a latitude is north or south, and the direction of a longitude is west or east. We can provide methods that ensure that the directions stored in a <latitude> instance are appropriate for latitude — and we can do the same for longitude. We show two techniques for implementing that type checking: See Section 10.6, page 128, and Section 19.5, page 318.

  • You can ask an object what its class is by using the object-class function. In this case, you can find out that an object is a latitude or longitude, rather than just a directed angle. The data does not stand alone; it is an instance that carries with it its type, its identity, and the methods appropriate to it.