Version 3 of Alternative for the API of Tcllib's geometry package

Updated 2021-02-04 08:52:33 by arjen

Arjen Markus (29 january 2021) Manfred Rosenberger (of rattleCAD fame) and I discussed the API of the geometry package in Tcllib. Currently the package uses names like calculateDistanceToLine for many of its procedures, the arguments being, respectively, a point and a line.

Note: this page is meant to discuss the ideas for an alternative API

Description of the alternative

While descriptive, the names are long and may be difficult to read/decypher. What is more, there is no flexibility in what object is the most important: in some cases you can have a bunch of points for which you need to determine the distance to a line, making the line the more important of the two and in other cases you may find that some point is the centre of the universe. Then the order of the arguments would be preferably the other way around.

Another drawback of the current API: it is not easy to get an overview of the procedures that are available for a particular type of object. At best this can be done by searching for the procedures that have "point" in their name. But it depends on the consistency of the names. For instance a rectangle can be constructed via the proc nwse, a name referring to the arguments, the nort-west and south-east corners.

A clear alternative could possibly be to use TclOO and create point and line objects, each with their own set of methods. But that feels as rather heavy. The geometrical objects we deal with are short-lived and they are never extended in any way. Creating actual objects in the object-oriented way would be overkill.

Manfred devised an alternative, based on namespace ensemble:

# Minimal example

package require math::geometry

namespace eval ::math::geometry::line {
    namespace ensemble create -parameter line
    namespace export distanceToPoint

    namespace import ::math::geometry::*

    proc distanceToPoint {line p} {
        ::math::geometry::calculateDistanceToLine $p $line
    }
}

namespace path ::math::geometry

set line  [list 0 0 1 0]
set point [list 1 1]
puts "Distance: [line $line distanceToPoint $point]"

By using the -parameters option of the namespace ensemble command we can emphasize the importance of the first argument - it is the central geometrical object.

Unfortunately, this becomes awkward for commands to "create" a new geometrical object:

   set point [point $x create $y]

For this type of commands we could use either of these:

   set p [create point $x $y]  ;# "create" a separate ensemble

   point p create $x $y ;# the first argument the name of a variable

So, basically, that is the idea:

  • Group the commands into separate ensembles
  • Provide alternatives where the "primary" object is the first argument
  • Reuse all the code in the geometry package and only/mostly provide wrappers to get a light-weight, object-oriented, API

Discussion

Is this a useful idea?

APN I like the idea behind ensembles but as a *personal* preference, I much prefer all the command and subcommand tokens to come at the beginning. So,

point create p $x $y

Also, not sure performance matters here but ensembles are slower to invoke than procedure calls (i.e. point create vs point::create). This is of course a nit, and I use ensembles all the time anyways.

AM Thanks for your comments - and the remark about performance. That would certainly be of interest, but we should measure the consequences of this particular style before we dismiss it :).

AM (4 february 2021) I tested this using the following little program":

# chkensemble.tcl --
#     Check properties of ensembles
#
package require math::geometry

namespace eval ::math::geometry::line {
    namespace ensemble create -parameter line
    namespace export distanceToPoint

    namespace import ::math::geometry::*

    proc distanceToPoint {line p} {
        ::math::geometry::calculateDistanceToLine $p $line
    }
}

#namespace eval ::math::geometry {
#    namespace export line
#}

#namespace import ::math::geometry::*
namespace path ::math::geometry

set line  [list 0 0 1 0]
set point [list 1 1]
puts "Distance - ensemble: [time {line $line distanceToPoint $point} 1000000]"
puts "Distance - direct:   [time {line::distanceToPoint $line $point} 1000000]"

The result was:

Distance - ensemble: 2.083693 microseconds per iteration
Distance - direct:   1.711088 microseconds per iteration

So using an ensemble is slower, by 20%, at least in this experiment, but the nice thing is that you can use either interface.