{ |one, step, back| }

The Shape Example in Dylan

Contributed by Scott Anderson

Dylan polymorphism is significantly different from that of other OO languages. Classes do use the interfaces of other classes and inherit slots (members). However, methods are defined independently of classes. Classes are strictly structures holding type information and variables.

Dylan methods are polymorphic. Each method belongs to a generic function of the same name, and the family of methods implements the function for each class. In other words, if you define a function family named "double", you can be assured that "double" probably means the same thing for each class for which a double method has been implemented. As such, methods exist in a global namespace (or within a module or library).

When you call a method, Dylan looks for the most specific definition for that method that matches the class(es) passed as the argument(s). In the shapes example, if there were a method "draw(shape :: )", when calling "draw(some-var)" Dylan would first look for "draw(var :: )". If it was not defined, Dylan then walks up the class inheritance tree for some-var until it finds a match.

In this way, Dylan can support multiple dispatch. If you define a method "foo(t1 :: , t2 :: )", and another "foo(t1 :: , t2 :: )", then calls to foo will perform the same specificity search as with single dispatch.

Internally to a method, you can call "next-method()", which will return the next more-general match of the method based on the argument types.

Dylan itself is a pain in the ass. The documentation is incomplete, links are broken everywhere, and some of the documentation even disagrees with other documentation. Dylan is not suitable for production work, as there are no commercial-quality compilers and the existing compiler is fairly difficult to use. Each program needs at least three files, defining compiler arguments, library imports and exports, and source. The compiler is also god-awful slow.

Code for Dylan

File: shapes.lid

library: shapes
executable: shapes
entry-point: shapes:%main

shapes-exports.dylan
shapes.dylan

File: shapes-exports.dylan

module: dylan-user

define library shapes
  use dylan;
  use format-out;
  use format;
end library;

define module shapes
  use dylan;
  use extensions;
  use format-out;
  use format;
end module;

File: shapes.dylan

module: shapes
synopsis: Dylan "shapes" example
author: Scott Anderson <baal@msen.com>
copyright: Copyright 1999, Scott Anderson  Permission granted to distribute freely.

define class <shape> (<object>)
        // initialize a shape
        slot x :: <integer>,
          required-init-keyword: x:;
        slot y :: <integer>,
          required-init-keyword: y:;
end class <shape>;

// absolute move origin to a newX and newY for shapes
define method move-to(shape :: <shape>, new-x :: <integer>, new-y :: <integer>) => ();
        shape.x := new-x;
        shape.y := new-y;
end method move-to;

// relative move origin by delX and delY for shapes
define method r-move-to(shape :: <shape>, del-x :: <integer>, del-y :: <integer>) => ();
        shape.x := del-x + x(shape);
        shape.y := del-y + y(shape);
end method r-move-to;

// rectangle class
define class <rectangle> (<shape>)
        // initialize a rectangle
        slot width :: <integer>,
          required-init-keyword: width:;
        slot height :: <integer>,
          required-init-keyword: height:;
end class <rectangle>;

// draw the rectangle
define method draw( rec :: <rectangle>) => ();
        format-out("Drawing rectangle at: (%D,%D), width: %D, height %D\n",
                   x(rec), y(rec), width(rec), height(rec) )
end method draw;

// circle class
define class <circle> (<shape>)
        // initialize a circle
        slot radius :: <integer>,
          required-init-keyword: radius:;
end class <circle>;

// draw a circle
define method draw( circ :: <circle>) => ();
        format-out("Drawing circle at: (%D,%D), radius: %D\n",
                   x(circ), y(circ), radius(circ) )
end method draw;

define method main(appname, #rest arguments)
        // throw the shapes in a sequence
        let scribble = make(<vector>, of: <shape>, size: 2);

        scribble[0] := make(<rectangle>, x: 10, y: 20, width: 5, height: 6);
        scribble[1] := make(<circle>, x: 15, y: 25, radius: 8);

        // loop through and display the magic of polymorphism
        for (s in scribble)
                draw(s);
                r-move-to(s, 100, 100);
                draw(s);
        end;

        // call a rectangle-specific function
        let r = make(<rectangle>, x: 0, y: 0, width: 15, height: 15);

        // note the use of the 'setter' syntax
        // "r.width := 30" and "width(r) := 30" are both equivalent
        width-setter(30, r);

        draw(r);

        exit(exit-code: 0);
end method main;

Output

Drawing rectangle at: (10,20), width: 5, height 6 
Drawing rectangle at: (110,120), width: 5, height 6 
Drawing circle at: (15,25), radius: 8 
Drawing circle at: (115,125), radius: 8 
Drawing rectangle at: (0,0), width: 30, height 15