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.
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).
- can be anonymous or external,
- have class(es) and optionally method definitions in the value,
- have attributes in their property list.
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
Let's define the class
+Shape using the
(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
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) )
T looks like a "constructor" in other programming languages, it should probably better be called "initializer". The
T method of
+Shape takes two arguments
Y, and stores them in the object's property list.
Our second method
move> changes the object's origin by adding the offset values
DY to the object's origin:
(dm move> (DX DY) (inc (:: x) DX) (inc (:: y) DY) )
> in the method name indicates that it is a method (as opposite to a normal function).
In the examples, we will notice a new variable
This and some new functions:
::. 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
:is usually followed by a property and can be read as "Get the value for this property from
=:can be read as as "Store a new value for a property of
::can be read as "Fetch the property cell of
In other words,
:: gets the
var of a cell. This is the same concept like the
set function that we discussed before.
gets the property
(=: x X)
sets the value of the object property
(inc (:: x) DX)
gets the value of
X and increases it by the value
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 +Rectangle +Shape) # dx dy
We see that we have two new properties for
dy. Additionally, we also have the
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
(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
Finally, let's define the methods
draw> for our
(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
draw> methods and a new method called
(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.