Thanks for Sharing

Previous Home Next

It's been a week since my last journal entry and while I've had no particularly strong inclinations to write an entry, I have been accumulating ideas that I might possibly work into an entry. I put the first installment of the journal web site on-line on Monday, August 19 and then sent the URL to friends and colleagues asking for feedback. I didn't expect much in way of immediate feedback given that these are the so-called dog days of summer (defined in terms of celestial mechanics as the the interval of time corresponding to twenty days on either side of the conjunction of Sirius (the dog star) and the sun and typically characterized by beastly hot and humid weather). In addition to being uncomfortably hot, most of the folks I sent the URL to are realizing that summer is almost over and it's time to enjoy the last few days before school starts or, if you really must, prepare for classes. I'm in the enviable position of being at the start of a year-long sabbatical. Even so I'm getting agitated as the fall semester nears - out of habit I suppose.

Among the several email messages that I did receive, I got some very useful suggestions (e.g., Aron, one of my former students, made the very reasonable suggestion that I provide a "resources" page so readers can find versions of the software I mention in the text for different platforms) and several tantalizing pointers that I couldn't resist following. Shriram Krishnamurthi told me about a library available in PLT Scheme that would have simplified the design of my software for building the journal web site and, in particular, my ad-hoc method of generating HTML for web pages. (Shriram also straightened out some of my misconceptions about XML.) I wasn't particularly interested in rewriting my code but I was interested in the abstractions implemented by the library.

Here's an illustration of the method that I used in the original version of builder.scm and which I used to build the first installment of the journal web site.

(define (original-method title text port)
  (fprintf port "~%~
<html>
  <head>
    <title>
      ~A
    </title>
  </head>
  <body vlink=\"#FFFFFF\" link=\"#669966\">
    ~A
  </body>
</html>~%" title text))

And here's how I would generate the same pages using the functions defined in the PLT Scheme XML library. XML is a language (technically, it's the syntax defining a language) for defining other languages. You can define a language like HTML in XML but many people think you can do better than HTML and XHTML is an attempt to do so. (See the World Wide Web Consortium (W3C) web pages on markup languages for more information about markup languages than you probably ever want to know.) The first expression below instructs Scheme to load the XML library.

(require (lib "xml.ss" "xml"))

(define (drscheme-method title text port)
  (display-xml/content
   (xexpr->xml
    `(html
      (head (title ,title))
      (body ((vlink "#FFFFFF") (link "#669966"))
            ,text)))
   port))

Ignore the outer functions like xexpr->xml and focus on the basic structure of the HTML formatting code. Each of the HTML tags, e.g., html, head and title have their counterpart expressions in Scheme. The syntax has changed but the meaning hasn't (actually, some would say that both have been considerably improved). Both implementations generate the exact same HTML code.

> (drscheme-method "Generating Web Pages" 
                   "Some text about generating web pages." 
                   (current-output-port))
<html>
  <head>
    <title>
      Generating Web Pages
    </title>
  </head>
  <body vlink="#FFFFFF" link="#669966">
    Some text about generating web pages.
  </body>
</html>

But there are two immediately obvious advantages of this reimplementation for a Scheme programmer such as myself. First, I need only concern myself with Scheme syntax for specifying the layout of the web page; I can use Lisp-style notation instead of HTML-style notation and so I can dispense with the HTML syntax involving the "less-than" (<) and "greater-than" (>) characters. Second, I don't have to remember to close off open tags and the tools for balancing parentheses built into my text editor should help me to catch most problems that involve establishing the scope (begin and end) of tags.

For the programmer used to hacking in both Scheme and HTML, my original version may be more appealing. There are times when you don't want to be shielded from syntax. However, usually when I'm hacking HTML, I use an editor that knows how to deal with HTML code, conveniently balancing tags and formatting text in accord with current best practices for HTML. When I'm editing Scheme I use a Scheme-savvy editor. Wouldn't it be nice if I could use a single editor that would know when to treat pieces of code as Scheme and when to treat pieces as HTML? Not surprisingly there exist such editors and DrScheme provides one that lets you nest alternating fragments of Scheme and HTML and edit each fragment in the appropriate language-savvy editor.

Shriram's email got me thinking about sharing code and other ways in which programmers facilitate working with one another and building on the results of other programmer's labors. When I use the term "hacker" I mean someone who enjoys and is good at programming. (I love the purported original definition of hacker, "someone who makes furniture with an axe", having made chairs, tables and large sculptures with an axe, hatchet, maul and both gas and electric chain saws.) Hackers in my experience tend to be an opinionated and individualistic lot and they tend to expect and appreciate strong opinions and independence of thought in others. The slogan for the Perl language is "There's more than one way to do it" [Wall et al., 200] and most hackers are open to exploring different ways of accomplishing the same task. That said, if adopting a standard way of doing something provides leverage for building better software, then most hackers will agree to adopt the standard, after much dickering about the details of course.

Last night I reread James Gleick's piece on debugging Microsoft Word for Windows (Word 2.0) entitled "Chasing Bugs in the Electronic Village" which first appeared in the New York Times Magazine ten years ago, August 1992, and is now the first chapter in Gleick's new book [Gleick, 2002]). As I was reading about Microsoft's marketing and software engineering glitches, I was thinking about my own checkered past in producing software for others to use. All of my software has been of an academic sort, proof-of-concept implementations of ideas appearing in technical papers, code implementing algorithms presented in an introductory text book, software products required by a research grant, or support code for projects and homework in courses I've taught. But I've had my share of disgruntled users over the years.

Assuming that you write code because you want other people to adopt your code, it behooves you to write your code in a language that other people are familiar with, adopt standard conventions for input and output so that people can interact with your code without learning some new set of conventions, and provide your code in a format so that others can use your code as a component in a larger project without having to understand all the details of your implementation. It's a struggle meeting these basic criteria for adoption and it requires the hacker to negotiate, make concessions and, generally, work within a community of software engineers and potential users to produce, adopt and adhere to reasonable standards. In terms of truth in advertising, I should mention that building great software takes a great deal of discipline in sharp contrast with the common stereotype of a hacker as an unkempt uncommunicative obsessive compulsive lacking basic hygiene and addicted to Jolt. I'm hopelessly out of date when it comes to modern software engineering practice. I know how to program in Java (more or less) but I don't think about programs from the perspective of object-oriented programming (which is often mistaken as the basis for modern software engineering practice). I'm pretty good at designing and implementing algorithms but I stink when it comes to structuring large programs.

In contrast with my early education in computer science, students today, budding software engineers, begin by being taught how to structure large programs. Many of them will never have to implement an algorithm of any complexity except as an academic exercise. The reason is that most of the algorithms and data structures that anybody might actually need are already implemented as libraries - or will be as soon as anyone recognizes their value. In designing a new program nowadays, you coopt most of the required algorithmic functionality by "including" or "importing" procedures and data structures from existing libraries. I'll illustrate some lessons from modern software engineering by reviewing some of the Neanderthal coding practices evident in my web site building software.

Object-oriented programming (or OOP as you'll often see it referred to) as a style of programming and a software engineering discipline is touted as facilitating the reuse and sharing of code, the encapsulation of expertise and the management of information by controlling access to implementation details. You don't need to program in an object-oriented programming language in order to achieve these benefits but the prevailing wisdom is that OOP makes them easier to achieve. My first introduction to object-oriented programming was an academic exercise in graduate school which required us to write a program in a language called SmallTalk. A little later I had to learn about object-oriented language extensions to a dialect of Lisp called Zeta Lisp in order to port some of my software to the Lisp Machine, a computer specially designed to run Lisp and made obsolete by the advantages of low-cost commodity PCs able to run any language. In neither of these two cases, did I really learn much about object-oriented programming; I just figured out how to get these languages to do what I usually did, namely, implement algorithms and data structures. Nor did I make any significant use of the touted advantages of object-oriented programming. Let me give you a simple example illustrating the benefits of controlling access to implementation details.

In designing the software for building the journal web site, I needed a data structure for storing information about each web page corresponding to a journal entry. This data structure which I referred to as a record would include a field called the entry corresponding to the relevant parts of the path in the file system leading to the files for the journal entry, a field called previous corresponding to the relevant parts of the path in the file system leading to the files for the chronologically prior journal, a field called next corresponding ... well, I expect you can guess, and fields for the date and title of the entry. I needed to be able to create such records, access their fields and modify selected fields. Here's how I implemented the record data structure using lists in Scheme. By way of background, the Scheme function list creates a list whose items are instantiated to be the values of the arguments in the invocation of the function. Lists in Scheme use zero-based indexes (the first item in the list has the index 0, the second has the index 1 and so on) and (list-ref record n) returns nth item (zero based) in the list corresponding record. (set-car! (list-tail record n) new-value) assigns the nth item (zero based) in the list corresponding to record to be new-value. The first function defined below is used to create a new record initializing all of the fields with values supplied as arguments. The next five functions are used to access the five fields in the record data structure returning the value of the specified field. The last two expression show two of the five functions used to modify the fields in the record data structure.

(define (make-record entry previous next date title)
  (list entry previous next date title))

(define (record-entry record) (list-ref record 0))
(define (record-previous record) (list-ref record 1))
(define (record-next record) (list-ref record 2))
(define (record-date record) (list-ref record 3))
(define (record-title record) (list-ref record 4))

(define (set-record-entry! record new-entry) 
  (set-car! (list-tail record 0) new-entry))
(define (set-record-previous! record new-previous) 
  (set-car! (list-tail record 1) new-previous))

I could also add functionality to the record data structure by defining functions for doing things with records. For example, it's often useful to have a print function for data structures that displays their fields clearly.

(define (print-record port record)
  (fprintf port
           "Record object:~%~
            Entry path          = ~A~%~
            Previous entry path = ~A~%~
            Next entry path     = ~A~%~
            Entry date          = ~A~%~
            Entry title         = ~A~%"
           (record-entry record) 
           (record-previous record)
           (record-next record) 
           (record-date record) 
           (record-title record)))

Here I create a new record, modify one of its fields (correcting the mistyped year in the date field), and then print the modified record.

> (define test-record
    (make-record "/02/08/24/" 
                 "/02/08/16/" 
                 "/02/08/28/" 
                 "August 24, 2001" 
                 "Sharing and Resuse"))
> (set-record-date! test-record "August 24, 2002")
> (print-record (current-output-port) test-record)
Record object:
Entry path          = /02/08/24/
Previous entry path = /02/08/16/
Next entry path     = /02/08/28/
Entry date          = August 24, 2002
Entry title         = Sharing and Reuse

This is all very well with the possible exception of the fact that it took a lot of code to do something that programmers have to do far too often. (Most modern dialects of Lisp have one or more methods of creating data structures. In PLT Scheme, the invocation (define-struct record (entry previous next date title)) does everything that the above implementation of the record data structure does and more. But the above implementation will help to illustrate the point I want to make.) However, a problem can arise when someone else tries to use my code. Suppose that Sulee Jenerika decides to use my record data structure in her code. Rather than copy my code into her program, she simply puts a require statement in her code that loads the file (library) which I've conveniently provided containing the code that implements my record data structure.

In Sulee's software project, she needs to be able to search for a record containing a particular entry and, having looked at my implementation of records, she writes the following procedures for operating on records. By way of background, the Scheme function assoc takes an object (a string corresponding to the path for an entry in this case) and a list of lists (a list of records in this case) and finds the first list in the list of lists whose first item is equal to the object. The function cons takes an object and a list and returns a new list whose first item is the object and whose remaining items are the items in the original list.

(define (record-lookup entry records)
  (assoc entry records))
(define (record-insert record records)
  (cons record records))

This works fine until I discover vectors, another primitive data type available in most programming languages and applicable in many of the same circumstances as lists. Let's suppose that I buy into some hacker's impassioned argument that I'll get better performance if I implement records using vectors. Which I do and then I update my record library expecting that everyone using my record data structure will be surprised when their code suddenly runs faster. Here's a glimpse at what the reimplemented record data structure might look like.

(define (make-record entry previous next date title)
  (vector entry previous next date title))

(define (record-entry record) (vector-ref record 0))
(define (record-previous record) (vector-ref record 1))
(define (record-next record) (vector-ref record 2))
(define (record-date record) (vector-ref record 3))
(define (record-title record) (vector-ref record 4))

(define (set-record-entry! record new-entry)
  (vector-set! record 0 new-entry))
(define (set-record-previous! record new-previous)
  (vector-set! record 1 new-previous))

And so on. My other record functions, e.g., print-record, work fine as written but Sulee's record-lookup and record-insert functions are badly broken; her implementation relies on the details of my initial implementation using lists. If Sulee used my code in a product and she then recompiles her code (including my record library) to produce the next release of her product, then her customers are likely due for some unpleasant surprises.

Is Sulee at fault for writing her extensions based on my initial implementation or am I at fault for changing my implementation? Clearly there's a need for some sort of a contract between library consumers and library producers that stipulates who is responsible for what. Object-oriented programming languages help to enforce such a contract by requiring that a library (called a class in the parlance of object-oriented programming) producer publish an interface specifying exactly how a library consumer is to use a given library, what functions to call and what behaviors to expect. Consumers for their part are expected not to assume anything about a library beyond what is specified in the interface. To enforce this discipline library producers can make certain parts of the implementation of a library private thereby making it impossible for a consumer to access those parts or exploit them in any fashion. As the producer of a library, I can modify the implementation as long as I preserve the interface. And, as a consumer of a library, I can rely on a stable interface, both in terms of the calling conventions for invoking procedures (the names of the procedures and the number, order and types of their arguments) and their resulting behavior.

When I learned Java a few years back, I got frustrated working my way through the two texts that I'd been recommended, [Arnold and Gosling, 1998] for a description of the language and [Budd, 2000] for an introduction to object-oriented programming in Java. My problem was that most of the functionality I was being introduced to I didn't need or at least I didn't think I needed. Why would I want to publish an interface describing what procedures I intend to write, why would I want to use or extend a class, why would I want to make some parts of my implementation public and other parts private, and why would I want inherit some parts of a class and override others? The answers to all of these questions have to do with sharing and reusing code and the benefits to be derived from sharing and reuse in the context of software engineering in the large - the art and science of writing, maintaining and improving large programs.

So I'm still not entirely sold and I expect I'll always want to implement everything from scratch rather than borrow someone else's code (my attitude here underscores one of the major obstacles to software reuse which is summed up in one of Alan Perlis's epigrams: "It is easier to write an incorrect program than understand a correct one"). But the fact of the matter is, if you're a mere mortal and you want to produce any interesting software, you'll probably have to make use of significant amounts of code written by other people. If nothing else all the compilers, editors, debuggers and other tools are written by others so why shouldn't you use well-crafted libraries to expedite and improve your own programming projects. Here's an example illustrating how object-oriented programming facilitates code sharing again based on my experience writing the code for building the journal web site.

One of the first things that you have to deal with in implementing an on-line journal involves specifying dates in terms of various calendars. I didn't think too much about calendars having resolved early on to simplify my life and use the Gregorian Reform Calendar. To give you a flavor for the potential confusion arising over dates specified in various calendars, consider that this is August 24, AD 2002 in the Gregorian (Reform) Calendar. Nowadays, you'll see the prefix AD/BC ("Anno Domini"/"Before Christ") traditionally appearing before the year replaced by CE/BCE ("Common Era"/"Before Common Era") appearing after the year, and, hence, this is August 24, 2002 CE. The year 2002 CE in the Gregorian Calendar is 5763 in the Jewish Calendar and 1423 in the Islamic Calendar. Noon today, August 24, 2002 CE at 12:00 UT ("Universal Time" which is sometimes referred to, now colloquially, as "Greenwich Mean Time" and abbreviated GMT), is 2452515 JD ("Julian Date") in the Julian Calendar in which dates are computed as a continuous count of days and fraction of days since noon UT on January 1, 4713 BCE. It makes your head swim. But having finessed this one aspect by fixing on the Gregorian Calendar, I was still left with plenty of room for ambiguity in dealing with dates.

The style of date format most common in the U.S. is mm/dd/yy which is easily confused with the European style date format dd/mm/yy. There are also alphanumeric formats such as August 24, 2002, Aug 24, 2002, and 24th of August 2002. I also encountered a proposed international standard, ISO 8601, for the representation of dates and times. In the ISO 8601 format, the string 2002-08-24T14:10:00-05:00 corresponds to August 24, 2002 at 2:10PM US Eastern Standard Time where the -05:00 specifies an offset from Coordinated Universal Time (UTC). Calculations involving dates specified in accord with ISO 8601, e.g., the difference in time between two dates, have to deal with time zones, leap years and even leap seconds. Luckily I was only concerned with years, months and days.

Most object-oriented programming languages, including Java and C++, have libraries, called classes, for calendars, dates, time zones, etc. A class specifies the mutable internal state, stored in fields, and the immutable procedures, called methods, for all objects or instances of the class. The Java Date class provides methods to determine if one instance of the class Date comes before or after another and whether two instances are equivalent as well as a host of other useful functionality. Date methods allow the user to extract the year, month, day, hour, minute, second and millisecond from an instance. If I were programming in Java, I'd just type "import java.util.Date;" at the top of my Java program and I'd be in business. But I'm a Lisp hacker and so I can build my own object-oriented programming language in Lisp (or use someone else's if I'm into sharing).

Fortunately, my latest thrall, PLT Scheme, provides a very nice library implementing classes and objects. The class.ss library mirrors much of the functionality found in Java only with lots of parentheses instead of curly brackets and semicolons. I'm going to simulate what it would be like to make use of and then extend a class implementing dates to illustrate how object-oriented programming supports sharing and reuse. First, let's look at what a PLT Scheme date class might look like (I'll use the capitalized Date for the Java class and the all-lower-case date for the PLT Scheme class). This is a simplified version of what's provided in the Java Date class but it's suitably small and yet illustrative for our purposes. I begin by specifying an interface which promises that any class implementing this interface will provide methods for getting the year, month and day of an instance of the PLT Scheme date class and a method for checking whether one instance is before or equal to another instance. In the Java Date class as well as the one I implemented in building an object-oriented version of builder.scm there were a lot more methods implementing all sorts of bells and whistles but I want to keep things simple for illustrating my point.

(define date-interface
  (interface () year month day before))
Java Version

In the next expression (below), I describe the class specifying the various fields of the class (init-fields are fields that are initialized when an instance of the class is created) and defining the methods specified in the indicated interface. The function definitions within a class description implicitly take as an argument an object of the class and they can refer to the fields of the class directly - all of these indirect and implicit references can be confusing to the uninitiated. The class date is said to inherit functionality from its superclass which is the base class (the simplest, most basic class possible referred to as object%). The class description indicates that all of the methods specified in the interface are public and hence accessible to anyone who imports the class. You can ignore the rest of the syntax for our immediate purposes.

(define date
  (class* object% (date-interface)
    (public year month day before)
    (init-field (this-year 0))
    (init-field (this-month 0))
    (init-field (this-day 0))
    (define (year) this-year)
    (define (month) this-month)
    (define (day) this-day)
    (define (before that) 
      (cond ((> this-year (send that year)) #f)
            ((< this-year (send that year)) #t)
            ((> this-month (send that month)) #f)
            ((< this-month (send that month)) #t)
            (else (< this-day (send that day)))))
    (super-instantiate ())))
Java Version

Below I create (instantiate is the term used to refer to creating an instance of a class) a couple of date objects and then invoke the before method illustrating the somewhat clunky syntax for invoking methods (Java has a much nicer syntax but then Java was designed from the ground up as an object-oriented programming language). An expression of the form (send object method arguments) invokes the method on specified object and additional arguments (the object is, as mentioned earlier, an implicit argument to the method). Java uses the convenient dot (".") operator so that the above invocation would appear as object.method(arguments) which now seems rather elegant to me but perhaps I've been brainwashed by looking at more OOP code than is healthy.

> (define date-one (instantiate date (2002 8 16)))
> (define date-two (instantiate date (2002 8 24)))
> (send date-one before date-two)
#t

In addition to implementing dates there is the messy problem of parsing strings to see if they contain dates and, if so, extracting the dates and converting them into the format needed to create an instance of the Date class. There is also the problem of formatting dates for display purposes. Some of this functionality is handled in the Java DateFormat class. The story of how dates, calendars, locales, time zones and the like are supported in Java is pretty interesting (see [Arnold and Gosling, 1998] for the details).

For purposes of illustration, let's pretend that someone else implemented the date class and I just want to make use of it. I'm going to do so by defining a new class, the journal-date class, which relies on or extends the date class. The journal-date class also implements an associated interface, the journal-date-interface, which extends the data-interface. Again don't sweat the details but you might be interested to know that super-instantiate refers to the procedure that instantiates the superclass, the date class in this case. Instances of the journal-date class deal primarily with strings and so I have to convert the strings to numbers for use in instances of the date class. Instances of the journal-date class, besides storing the original strings presumably extracted from fragments of text that I wrote, also perform the service of reformatting the date according to the ISO 8601 standard.

(define journal-date-interface
  (interface (date-interface) iso))
Java Version
(define journal-date (class* date (journal-date-interface) (public iso) (init-field (year-string "")) (init-field (month-string "")) (init-field (day-string "")) (define (iso) (format "~A-~A-~A" year-string month-string day-string)) (super-instantiate ((string->number year-string) (string->number month-string) (string->number day-string)))))
Java Version

And a simple test to make sure that everything works as advertised with instances of the journal-date class responding to methods inherited from the date class as well as to those, well one, implemented in the journal-date class.

> (define journal-date-one (instantiate date ("2002" "08" "16")))
> (define journal-date-two (instantiate date ("2002" "08" "24")))
> (send journal-date-one before journal-date-two)
#t
> (send journal-date-one iso)
"2002-08-24"

Real classes are much more complicated than my simple examples suggest. If real classes were so simple to write, then there wouldn't be much point in sharing them. A good class is well documented and easy to extend or tailor to the needs of a specific project. It does take effort to understand someone else's code, but in the case of well-crafted and widely-used classes the benefits in terms of better performing and more easily shared, maintained and extended software can more than offset the disadvantages. Way better than sliced bread.