5.7 Use of next-method to call another method

Notice that there is duplication of code in the two methods for say, as shown in Figure 5.5. Both methods call decode-total-seconds to get the hours and minutes, and call format-out to print the hours and minutes. Both methods print a leading zero for the minutes, if appropriate. These two tasks are all that the method on <time-of-day> does. The method on <time-offset> does a bit more; it prints either minus or plus, depending on the value of the past? slot.

We can eliminate this duplication by defining another method that does the shared work. This method will be on the <time> class, so it will be applicable to instances of <time-of-day> and <time-offset>. The method for <time-of-day> is no longer needed, because the new method does the same work. However, a revised method for <time-offset> is needed, to do the extra work of printing minus or plus, and to call the method on <time>, which is the next most specific method.

You can use the next-method function to call the next most specific method. Recall that the result of Dylan's method dispatch procedure is a list of applicable methods, sorted by specificity. When one method calls the next-method function, Dylan consults the list of sorted methods and invokes the next most specific method on the list. (It is an error to call next-method from the least specific method.)

We remove the definitions of the existing say methods, and define these new methods:

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;
define method say (time :: <time-offset>)
  format-out("%s ", if (past?(time)) "minus" else "plus" end);
  next-method();
end method say;

We can call say:

? say(*my-time-of-day*);
0:02

In the preceding call, the argument is of the type <time-of-day>, so the method on <time> is the only applicable method. That method is invoked.

? say(*my-time-offset*);
plus 15:20

In the preceding call, the argument is of the type <time-offset>, so two methods are applicable. The method on <time-offset> is more specific than is the method on <time>, so the method on <time-offset> is called. That method on <time-offset> prints minus or plus, and calls next-method. The next-method function calls the method on <time>, which prints the hours and minutes.

Using next-method is convenient in cases such as this, where a method on a superclass can do most of the work, but a method on a subclass needs to do additional work.

When next-method is called with no arguments, as it is in the method on <time-offset>, Dylan calls the next most specific method with the same arguments provided to the method that calls next-method.

You can provide arguments to next-method. For example, you could provide a keyword argument with a value that each method can manipulate (such as adding a value to a number, or appending an element to a list). If you provide arguments to next-method, the arguments must be compatible with the generic function, as described in Section 12.2.5, page 176. In addition, you cannot supply required arguments that have classes different from those of the original required arguments to the generic function, if doing so would have changed the method dispatch in any way. Providing arguments to next-method is an advanced technique; see Section 12.2.3, page 172, and Section 17.2.2, page 260.