s c h e m a t i c s : c o o k b o o k

/ Cookbook.IntroductionToMzlibClasses

This Web


WebHome 
WebChanges 
TOC (with recipes)
NewRecipe 
WebTopicList 
WebStatistics 

Other Webs


Chicken
Cookbook
Erlang
Know
Main
Plugins
Sandbox
Scm
TWiki  

Schematics


Schematics Home
Sourceforge Page
SchemeWiki.org
Original Cookbook
RSS

Scheme Links


Schemers.org
Scheme FAQ
R5RS
SRFIs
Scheme Cross Reference
PLT Scheme SISC
Scheme48 SCM
MIT Scheme scsh
JScheme Kawa
Chicken Guile
Bigloo Tiny
Gambit LispMe
GaucheChez

Lambda the Ultimate
TWiki.org

(lib "class.ss") Notes


These are a set of notes I'm taking about learning the class system in mzlib, the standard library that comes with mzscheme. I'm not already that familiar with it; for me, it's always a little hard for me to learn from the existing reference documentation:

http://download.plt-scheme.org/doc/301/html/mzlib/mzlib-Z-H-4.html#node_chap_4

but that's precisely because it's doing its job at being a very terse and dense repository of knowledge.

But since I'm also a bit dense, I'll try to write a leisurely-paced tutorial on the class system, targeted toward Java or Python programmers; it should help people who come from those backgrounds to get a minimal foothold on using the standard OOP framework in mzscheme. Basically, this is the beginner's guide I wish someone had written for me.

There's another guide to the OOP system that may be very helpful to people: Scheme with Classes, Mixins, and Traits.

The OOP system in mzscheme is extensive enough that there are several ways to express the same thing, and there are other places where the system goes beyond the support that "mainstream" languages provide. So these notes are certainly not supposed to be comprehensive. Also, I'm still a newbie, so some of what I write might be horribly wrong. Feedback and corrections will be greatly appreciated.

I'll try to maintain a list of links on the bottom for supplementary material.

First steps


Like many of the subsystems in mzscheme, the class system is actually a part of mzlib, and it's a bit surprising to realize that it's treated as an optional add-on, just like any other module library.

If we start off mzscheme and try something obvious, like: 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

we see that class isn't even a syntactic keyword at this point. So let's begin by pulling in the class support.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Ok, that's progress, since we now see that the error message has changed to say that we're just writing something syntactically silly.

Classes are people too


But now that we have class support, let's first start off with a simple toy example. In Java, we might see a beginning class like:

    public class Person extends Object {
        public String name;

        public Person(String name) {
            this.super();
            this.name = name;
        }

        public void sayHello() {
            System.out.println("hello, my name is " + this.name);
        }
    }

which demonstrates how to define a Java class with a constructor and a simple method for salutation's sake. [Side note: In Java, some of the things above can be left out, like the extends Object part. But to make things easier to see in comparison to mzscheme's class system, I'm making those constructs explicit. Also, we're making name a public attribute for the moment, though we'll touch on privacy in just a moment.]

Let's see what this looks like in mzscheme:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Ok, this is not too different from Java. The conventional way to name a mzscheme class is to use a % suffix on the class name, and I'll follow that convention for the rest of this tutorial. We also see that there is already a built-in object% base class:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Ok, now that we have a simple person% class defined, how do we make people? In Java, we'd say:

    Person p = new Person("mccarthy");

In mzscheme, we can fire off an instantiation similarly:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

new is a special form that allows us to make instances of classes, and as we can see here, it can take class name and parameters that we use to initialize our instance and its fields.

There are other ways of instantiating objects:

The differences between these forms has to do with the way we pass initial parameters to our instances. new gives us the option to pass things via keywords, make-object allows us to pass values positionally, and instantiate allows both positional and keyword arguments.

For these notes, we'll be using new just because, well, I want these notes to be as simple as I can.

All subclasses must call the subclass's super initializer, just to ensure that everything's initialized properly. But out of curiosity, what happens if we don't? Let's make a quick class that doesn't call super-new.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

We don't see anything break yet, but if we start trying to use the class, we'll see problems:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

So we end up seeing a runtime error during instance instantiation, and the error message accurately reflects this.

(Also note that the error message mentions "instantiate" --- I guess this means that new expands out to a call to the more general instantiate form.)

Of course, once we have such an instance, we need to know how to fire messages off to an instance. In Java, we do this by using dot-notation:

    p.sayHello();

In mzscheme, we use the send form to send a message off:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

And that's our first example.

(when ((new person% (name "harry")) . met . (new person% (name "sally"))))


Let's try another person% example where people can meet other people.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Like the first example, we define each person to have a name, but we also give them a list of friends. And because these people didn't live through the liberating sixties, a person's list of friend's is a bit closed and private.

Let's make a few people, and have a social gathering.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

One thing to notice that get-field lets us peek into the public fields of a class:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

But because friends isn't defined to be a public field, we can't just dig into a person's mind and violate their privacy.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

We can get a little creepy, though, and can make it so that this is open:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

where friends is now a public field.

Following on that idea, if we are used to enforcing object encapsulation, having all this openness is a bit disconcerting. If we're really paranoid, we can go to an extreme and do something like this:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

And now we have something that simulates really strict privacy.

But a more idiomatic way of doing this uses an intialization parameter that then serves as the base of a private field. 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

in which case we only use name here as the public-facing interface to an instance's instantiation. Internally, we stick with -name. This uses the init form to break things down to this level of granularity.

Inheritance


Let's look at a person% definition that has the features we've talked about so far:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

But isn't it peculiar that everyone answers the same way? One limitation of person% is that a person will say-hello with the same generic phrase, and that a person% will meet-all in the same way. That is what a class is all about --- to define a regular behavior --- but sometimes, we'd like to extend that behavior.

Let's add some diversity. Let's say that we'd like to create another kind of person that behaves the same as the person%, well, except that their lingo is slightly different. Let's see what that might look like:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Here we have a new class called valley-person% that extends our person% class. By extension, we mean that it does everything a person% would do, except in the cases that we explicitely override.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

In contrast to Java, we have to be a little more explicit when we relate to our superclass. In particular, any superclass fields that we'd like to access from our subclass are the ones that we'll name using inherit-field. Furthermore, any functions we'd like to override will be marked by our use of the define/override form.

Let's bring someone else into the party.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Hmmm.. luke is not quite as warm to leia as leia is. Our version of valley-person% is not gregarious in the sense that add-friend is a bit asymmetric. Let's fix that.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Now our valley-person% can do-lunch with another person.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Gnarly. These people do have a particularly dark-sided way of meeting each other. Sins of the father superclass, I suppose.

One thing to note is that when leia does lunch with luke, she *send*s herself an add-friend message, and also gets luke to add herself as a friend. The identifier this is there so that leia can send messages to herself.

Another thing to realize is that this message passing is all being done at run-time: we could just as easily ask leia to try dancing, with an error message showing up only as the point where we call send:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

An alternative approach to the above class definition looks like this, with one less this:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

The difference here is that our first call to add-friend explicitly reuses the add-friend method by inheritence. Rather than use send to trigger an external message to ourselves, we're directly calling our inherited method.

Why would we want to call inherited --- actually, any! --- methods this way, if we already have send? One advantage of doing it this way is that valley-person% can check at class-creation time that its superclass does have an add-friend method, so that we can do earlier error trapping. That is, although:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

will compile fine and error out when we call do-lunch, the alternative will break as soon as we try defining the class:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Early error messages are nicer than late ones. By using the second form ---- by using inherit to access methods on this, we can add an additional level of checking to catch typos as early as we can. inherit obligates the superclass to have the method we'd like to inherit.

(In general, we should consider send to be the message passing form we use to send external messages to other objects.)

This might seem a little weird to Java people: isn't it just blindingly obvious by looking at the superclass what methods belong in there? A class must have a fixed superclass, right? It's right in the class definition! In Java, this would be true. But things can be different in mzscheme, and that's what the next section is about.

Mixing things up with Mixins


We're going to mix things up by changing our domain from people to containers. One common thing that people might like to do is "fold" across lists or vectors. Let's write this:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

And let's try this out.

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Ok, looks good so far. With this, we might later want to reusing these classes, but with some additional functionality. For example, what if we'd like to have for-each?

One's immediate approach might be to make a superclass that holds the common functionality. But let's make ourselves an artificial restriction, just for the sake of playing things out: let's say that we restrict ourselves from touching list% or *vector%*'s definition. What then?

If we tie ourselves to this, then one approach might be to subclass:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

But this feels a little foolish. Don't we hate code duplication?

Of course there's another approach. Let's look at it:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

This is something very new if we're coming from Java, and probably very surprising! We're taking in a superclass, and "mixing in" a few more methods to create a new class. Now we don't have to repeat ourselves, and things still work out:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

The real kicker here is that foreach-mixin can take in anything that implements a fold, and spit out a new class that also implements a for-each method.

Of course, the mixin depends on fold: it would also be silly to apply this on people, but if we try it out:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

... we don't get an error! It just means we have a valley-person2% that is bogus. Can we do better?

Let's fix this and restrict the mixing to superclasses that provide fold, by using the inherit form again from the previous section:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

With this version, we'll catch errors a little more quickly:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

Mixins provide us a robust mechanism for adding functionality in a general way, and they're used quite a bit in DrScheme's internals.

We can do even better if we use interfaces, which haven't been covered yet. [fixme: but they should!]

Other Resources


There's much more to the class system than what's covered here. The paper below is an excellent guide to the features that set (lib "class.ss") far apart: D. S. Goldberg, R. B. Findler, and M. Flatt. Super and Inner --- Together at Last! (http://library.readscheme.org/page4.html)

More recently, Scheme with Classes, Mixins, and Traits provides an excellent overview of the system.

Also, the Schematics Cookbook has notes on how to do OOP programming for Scheme in general. See IdiomObjectOrientedProgramming for more details.

There's a brief example of mixins in: Modular Object-Oriented Programming with Units and Mixins http://www.cs.utah.edu/plt/publications/icfp98-ff/

The paper Classes and Mixins give further examples of mixin-based programming Classes and Mixins http://www.cs.brown.edu/~sk/Publications/Papers/Published/fkf-classes-mixins/


Comments about this recipe

I wonder if anyone has figured out how to do default arguments yet? I can't figure it out from the official docs.

-- HaraldKorneliussen - 11 Aug 2006

Done; added a small recipe about this here: DefaultArgumentsClassInitialization

-- DannyYoo - 4 Sep 2006

''Singleton Design Pattern using class.ss,''

Matthias was kind enough to reply to my query to the list with the following:

> One of the things you can do is instantiate a class once and export
only that instance:

500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)

> Inside the class you never use new; instead you refer to this
instance.

-- StephenDeGabrielle - 12 Dec 2007

Contributors

-- DannyYoo - 19 Apr 2006

CookbookForm
TopicType: Recipe
ParentTopic: GettingStartedRecipes
TopicOrder:

 
 
Copyright © 2004 by the contributing authors. All material on the Schematics Cookbook web site is the property of the contributing authors.
The copyright for certain compilations of material taken from this website is held by the SchematicsEditorsGroup - see ContributorAgreement & LGPL.
Other than such compilations, this material can be redistributed and/or modified under the terms of the GNU Lesser General Public License (LGPL), version 2.1, as published by the Free Software Foundation.
Ideas, requests, problems regarding Schematics Cookbook? Send feedback.
/ You are Main.guest