Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

A Taste of Dylan


OCT92: A TASTE OF DYLAN

David is a DDJ contributing editor and the author of XLisp, XScheme, Bob, and numerous other programming languages. He can be contacted through the DDJ offices.


Dylan is a new language designed by the Advanced Technology Group at Apple Computer. Like C++, Dylan is an object-oriented language. Unlike C++, Dylan is a DYnamic LANguage. This means that it provides automatic storage management as well as runtime type checking and dynamic linking. It shares these features with other dynamic languages such as Lisp and Smalltalk.

In fact, Dylan looks like a cross between Scheme (a small but powerful dialect of Lisp) and CLOS (the Common Lisp Object System). Like Scheme, Dylan is a compact language. It is designed to be compiled efficiently and to run on machines with limited resources. This is in contrast to Common Lisp, which typically requires many megabytes of memory.

Dylan's Object System

One of Dylan's most notable features is its object system. Like CLOS from Common Lisp, it supports multiple inheritance and provides polymorphism through generic function calls. With Dylan, however, the object system goes all the way down to the primitive data types. In Common Lisp, the object system was grafted on top of a nonobject-oriented language. Dylan is "objects all the way down," like Smalltalk.

Example 1 shows an example of a Dylan class definition, which creates a class and stores its definition as the value of the symbol <point>. The class inherits from the class <object> and has slots named x and y. You can create a new instance of the class <point> by passing it as an argument to the make function along with any initialization keywords. In this case, x: and y: are used to provide initial values for x and y, respectively. You can create a point with the coordinates (12,23) using the form: (make <point> x: 12 y: 23).

Example 1: A Dylan class definition.

  (define-class <point> (<object>)
    (x required-init-keyword: x:)
    (y required-init-keyword: y:))

In addition to defining a new class, the define-class form also creates getter and setter functions for each of the slots. In Dylan, slot values are always accessed through functions. The getter function gets the value of a slot and the setter function sets its value. The default names for these functions are derived from the name of the slot. The default getter function name is simply the slot name. The default setter function name is the list (setter <slot-name>). You can override these defaults using slot options.

In Example 2(a), for instance, setter functions are used to change the value of a slot. You can call the setter function directly, as in Example 2(b). But it is much more common to use the form in Example 2(c).

Example 2: (a) Using setter functions to change the value of a slot; (b) calling the setter function directly; (c) an alternate use of the setter function.

  (a)

  (define foo (make <point> :x 12 y: 23))
  (x foo) => 12
  (y foo) => 23

  (b)

  ((setter x) foo 99)

  (c)

  (set! (x foo) 99)

The symbol required-init-keyword: is called a "slot option." It states that the keyword that follows is used to initialize its associated slot and that the keyword must be present in any call to make for the class <point>. Table 1 lists other slot options that Dylan supports; Table 2 lists space-allocation options.

Table 1: Dylan slot options.

  Slot Option     Purpose
  ------------------------------------------------------------------------

  getter:         Specify the getter function name.
  setter:         Specify the getter function name.
  type:           Specify the type of values to be stored in the slot.
  init-value:     Specify the initial value of the slot.
  init-function:  Specify a function to compute the initial value of the
                   slot.
  init-keyword:   Specify the make function keyword used to initialize the
                   slot.
  allocation:     Specify how space for the slot is to be allocated.

Table 2: Dylan space-allocation options.

  Option         Purpose
  ------------------------------------------------------------------------

  instance       Each instance has storage for the slot.
  class          Every instance of the class and its subclasses share the
                  same storage for the slot.
  each-subclass  The class and each-subclass get their own storage for the
                  slot, each instance uses the storage associated with its
                  direct class.
  constant       The value of the slot is a constant.
  virtual        The slot has no storage allocated for it and its value
                  must be managed by its getter and setter functions.

A class can be defined as a subclass of several other classes. In this case, the new class inherits the structure and behavior of all its parent classes. For instance, you may want to define a subclass of <point> that allows a bunch of points to be linked together into a doubly linked circular list. You could define a subclass of <point> with two additional fields, next and prev, but this would bury the definition of the doubly linked circular lists in the definition of linked-point. It would be better to separate out that functionality to make it available for building other types of doubly linked lists. You can do this by defining a mixin class, a class that isn't really useful by itself but can be mixed into other classes to give them additional functionality. Example 3(a) shows a definition of the doubly linked list mixin. Example 3(b) then defines the <linked-point> class. Objects of the new class <linked-point> will contain the slots of both <point> and <linked-entity>. All slot accessors for both classes will work on instances of the new subclass, and any methods that apply to either superclass (or their superclasses) will apply to instances of the subclass.

Example 3: (a) A definition of the doubly linked list mixin; (b) a definition of the <linked-point> class.

  (a)

  (define-class <linked-entity> (<object>)
   next
   prev)

  (b)

  (define-class <linked-point> (<point> <linked-entity>))

Polymorphism

One of the important concepts of object-oriented programming is polymorphism. Polymorphism makes it possible to customize the behavior of a function based on the type of its arguments.

Dylan implements polymorphism through generic functions, collections of methods that understand how to handle particular types of arguments. When a generic function is applied to arguments, the generic-function dispatch facility selects an appropriate method based on the classes of the arguments passed in the generic function call.

Unlike message-passing languages such as Smalltalk that only dispatch on the class of the object receiving the message, Dylan takes into account the classes of all of the required arguments to a function. This is called "multiple dispatch" (or "multimethods"), and makes it much easier to define functions whose behavior depends on more than one of their arguments. For instance, a print function could behave differently based on both its target output device and the type of object being printed.

Method Definitions

Now, it's time to look at method definitions. Example 4 defines a few methods useful for classes that use the <linked-entity> mixin. The first method is used to initialize a new instance of the class. It simply sets the next and prev pointers to the object itself. This creates a doubly linked circular list of one element. Note that initialize calls next-method before doing anything else. The next-method function calls the next applicable method, which would have been called if this initialize method did not exist. This is useful for adding behavior in a subclass since it causes an inherited method to be called. Dylan doesn't support before, after, and around methods like CLOS, so this is the primary means of method combination.

Example 4: Defining methods for classes that use the <linked-entity> mixin.

  (define-method initialize ((x <linked-entity>))
    (next-method)
    (set! (next x) x)
    (set! (prev x) x))

  (define-method add-after ((e1 <linked-entity>)
                (e2 <linked-entity>))
    (bind ((e3 (next e1)))
      (set! (next e1) e2)
      (set! (next e2) e3)
      (set! (prev e3) e2)

      (set! (prev e2) e1)))

The second method adds the element e2 to a list immediately after the element e1, updating the links accordingly.

Methods usually have one or more required arguments, whose classes are used to select the method when it is associated with a generic function. In the add-after method, both e1 and e2 are required arguments.

Each required argument may have a specializer that restricts the objects that can appear as the value of that argument. In the definition of add-after, the specializer (e1 <linked-entity>) says that the first argument is called e1 and its value must be a general instance of the class <linked-entity>. (A general instance of a class is an instance of either the class itself or one of its subclasses.) Even when you call a method directly, the constraints implied by the specializers are enforced. It's impossible to call a method with arguments that don't match its argument specializers.

In addition to required arguments, methods can have optional keyword arguments and rest arguments. Keyword arguments are passed in pairs. The first element of the pair is the keyword, and the second is the value. You can also specify a default value to be used if the keyword argument isn't specified.

Sometimes it is convenient to allow a function to take an arbitrary number of arguments. This is handled by the rest argument. When you call a method with a rest argument, all of the arguments beyond the required arguments are collected together and passed as a sequence as the value of the rest argument. The method is then free to examine these additional arguments using the sequence functions provided by Dylan. When a method accepts both keyword arguments and a rest argument, the same actual arguments are used to satisfy both.

Dylan allows methods to return more than one value. For instance, if you want to define a method for <point> that returns both coordinates, you do it as shown in Example 5(a). This method will return two values, the x and y coordinates. To use the two values, you can use the bind form to bind the values to variables; see Example 5(b).

Example 5: (a) Defining a method for <point> that returns both coordinates; (b) binding the two values in (a) to variables.

  (a)

  (define-method coordinates ((p <point>))
  (values (x p) (y p)))

  (b)

  (bind ((x y (coordinates foo)))
    ...  do something with x and y ...
  )

All object-oriented languages allow instance variables or slots to be associated with a class of objects. Dylan also allows slots to be associated with particular instances. This can be handy when you don't want to create a new class that will have only one instance just to allow that instance to have a new slot. You can also define a method that applies only to a particular instance using what Dylan calls a "singleton." A singleton can be used in place of a class in the specializer for an argument to a method and will cause the method to apply only to that particular instance.

Conclusion

Dylan has many other interesting features that I haven't discussed here: object-oriented condition handling, a standardized protocol for handling iteration, introspective functions for allowing programs to inspect the way classes and methods work, and so on. I hope this article has given you a flavor of this new object-oriented, dynamic programming language from Apple.

Incidentally, you can get a copy of the Dylan manual by writing to: Apple Computer Inc., One Main Street, Cambridge, MA 02142.


Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.