PicoLisp Explored: Object-Oriented Programming, Part 1

PicoLisp Explored: Object-Oriented Programming, Part 1

"Object-oriented programming is an exceptionally bad idea which could only have originated in California." (Edsger W. Dijkstra)

·

6 min read

Today we will explore the object-oriented programming possibilities in PicoLisp.

Despite the crushing judgements of Edsger Dijkstra ("I don't think object-oriented programming is a structuring paradigm that meets my standards of elegance") we will also find some cases where OOP is actually very useful - of course!

So let's be open minded and check out the object oriented concepts of PicoLisp.


What is Object Oriented Programming?

Object-oriented programming is a programming paradigm that centers around the concept of "objects" that can contain data (often called properties) and code (often called methods). The objects are instances of certain classes and inherit their classes' properties and methods.

PicoLisp comes with built-in object oriented extensions. There seems to be a common agreement upon three criteria for object orientation:

  • Encapsulation: Code and data are encapsulated into objects, giving them both a behavior and a state. Objects communicate by sending and receiving messages.
  • Inheritance: Objects are organized into classes. The behavior of an object is inherited from its class(es) and superclass(es).
  • Polymorphism: Objects of different classes may behave differently in response to the same message. For that, classes may define different methods for each message.

Like all programming paradigms, OOP has cases where it's helpful and others where it's rather producing spaghetti code. We will not discuss the pros and cons here. Let's focus now on how these rather abstract definitions can look like in actual code.

bonding.png


Objects and Classes

The following information is partially based on the PicoLisp64 OOP-tutorial (PicoLisp64 is the predecessor of pil21). Unfortunately the tutorial is not included in pil21 anymore, therefore I have uploaded the HTML and example files to Gitlab.


Classes are defined using the class function. A class

  • has a name,
  • has method definitions and superclass(es)
  • may have class variables (attributes).

Objects

  • can be anonymous or external,
  • have class(es) and optionally method definitions in the value,
  • have attributes in their property list.

Naming conventions

Per convention, class names start with a +, and class methods end with a >. Let's take a look at some examples.


A simple base class

Let's take a look at the example from the PicoLisp64 tutorial: A simple base class called +Shape and two subclasses +Rectangle and +Circle.

Let's define the class +Shape using the class function:

(class +Shape)
# x y

Per convention, the class properties should be added as comments directly after the class function. As PicoLisp is a dynamic language, a class can be extended at runtime with any number of properties, and there is nothing like a fixed object size or structure. This comment is a hint of what the programmer thinks to be essential and typical for that class. In the case of +Shape, x and y are the coordinates of the shape's origin.

Now let's add two method definitions using the function dm ("define method"). Let's start with the method T, which is a special method: Each time a new object is created, and a method with that name is found in its class hierarchy, that method will be executed.

(class +Shape)
# x y

(dm T (X Y)
   (=: x X)
   (=: y Y) )

Though T looks like a "constructor" in other programming languages, it should probably better be called "initializer". The T method of +Shape takes two arguments X and Y, and stores them in the object's property list.


Our second method move> changes the object's origin by adding the offset values DX and DY to the object's origin:

(dm move> (DX DY)
   (inc (:: x) DX)
   (inc (:: y) DY) )

The > in the method name indicates that it is a method (as opposite to a normal function).


Using This

In the examples, we will notice a new variable This and some new functions: :, := and ::. What do those stand for?

  • This: When we are dealing with objects, we often have the case that we want to refer to the current object. In this case, we can use the This variable.

  • : is usually followed by a property and can be read as "Get the value for this property from This".

  • =: can be read as as "Store a new value for a property of This".

  • :: can be read as "Fetch the property cell of This".

In other words, :: gets the var of a cell. This is the same concept like the set function that we discussed before.


Some examples:

   (: x)

gets the property x.

   (=: x X)

sets the value of the object property x to X, and

   (inc (:: x) DX)

gets the value of X and increases it by the value DX.


Subclasses and Inheritance

Now back to our example. Let's create a subclass of +Shape - our rectangle class +Rectangle. To indicate that +Rectangle inherits from +Shape, we add the latter one to the class function:

(class +Rectangle +Shape)
# dx dy

We see that we have two new properties for +Rectangle: dx and dy. Additionally, we also have the x and y property that are inherited.

+Rectangle inherits the methods of Shape, for example the move> function. However, we need to adapt our initializing function T to the increased properties. We can do that by calling the super function:

(dm T (X Y DX DY)
   (super X Y)
   (=: dx DX)
   (=: dy DY) )

This shows that the super class already has a T method that uses the X and Y arguments.

Finally, let's define the methods area>, perimeter> and draw> for our +Rectangle.

(dm area> ()
   (* (: dx) (: dy)) )

(dm perimeter> ()
   (* 2 (+ (: dx) (: dy))) )

(dm draw> ()
   (drawRect (: x) (: y) (: dx) (: dy)) )

A second subclass

Let's create a second subclass called +Circle. It also inherits from the superclass +Shape and has an additional property for the radius, r. Just like for our rectangle class, let's define our area> and draw> methods and a new method called perimeter>.

(class +Circle +Shape)
# r

(dm T (X Y R)
   (super X Y)
   (=: r R) )

(dm area> ()
   (*/ (: r) (: r) 31415927 10000000) )

(dm perimeter> ()
   (*/ 2 (: r) 31415927 10000000) )

(dm draw> ()
   (drawCircle (: x) (: y) (: r)) )

31415927 of course stands for pi. If you are confused why there is no decimal point and don't know the function */, you will find help in the post about Fixed Point Arithmetics.


Now we know how to define classes, subclasses, methods and properties. In the next post we will see how to instantiate objects from these and how use them.

You can find the full code for the example here.


Sources

xkcd.com/1188
PicoLisp64
software-lab.de/doc/ref.html#oop
Cover Pic: Circle, Triangle, Rectangle by the Japanese monk Sengai