PicoLisp Explored: Object-Oriented Programming, Part 2

PicoLisp Explored: Object-Oriented Programming, Part 2


5 min read

In the previous post, we showed how to define classes and their methods and properties in PicoLisp using the class and dm functions.

We have already created the classes +Shape and its two subclasses +Rectangle and +Circle with a few methods. You can find the code here or in the previous post. As you might remember, per syntax convention class names start with a + and methods end with a >.

Now we will show how we can create objects from that classes and use them. Let's open our file with the class definitions and run it with: $ pil shape.l + (the + opens the debugger).

Let's create a rectangle object!

We can create a new object using the function new. A rectangle object also has four properties, X Y DX DY that also need to be defined in the function call. For example, the code below defines a rectangle with width=30 and height=20, with the reference point (x,y) in (0,0).

(new '(+Rectangle) 0 0 30 20)

Let's set it to the variable R.

: (setq R (new '(+Rectangle) 0 0 30 20)) 
-> $134432824

The return value is a so-called "anonymous symbol". However, it's not so relevant for the moment, so we will not dig further into that now.

We can check R using show:

: (show R)
$134432824 (+Rectangle)                   # Show the rectangle
   dy 20
   dx 30
   y 0
   x 0

Use the methods

Now let's use the area> and perimeter> functions that we defined before. The syntax is pretty straightforward:

: (area> R)                               # Calculate area
-> 600

: (perimeter> R)                          # and perimeter
-> 100

Now let's move the origin:

: (move> R 10 5)                          # Move 10 right and 5 down
-> 5
: (show R)
$134432824 (+Rectangle)
   y 5                                    # Origin changed (0,0) -> (10,5)
   x 10
   dy 20
   dx 30

Note that although the method move> was not defined for the +Rectangle class, it's still available since it is inherited from the +Shapesuperclass.

We can do the same also with the circle class. Since it is really identical, I will not write it here, but you can check it in the source code of the tutorial.

Applying list functions

The objects can be used like any other symbols. For example they can be grouped to lists and we can apply list functions on them.

As an example, let's group the rectangle R and the circle C to a list and create a new list with their respective areas by applying mapcar 'area> on them:

: (mapcar 'area> (list R C))
-> (600 2827)

Or we could move all list items by 10 items down and right. Let's do this with an anonymous function:

: (mapcar 'area> (list R C))              # Get list of areas
-> (600 2827)

: (mapc
   '((Shape) (move> Shape 10 10))         # Move all 10 right and down
   (list R C) )
-> 25

mapcar and mapc both apply a defined function to each element of a list. The difference is that mapcar returns the whole list while mapc rertuns only the result of the last evaluation. Here you can find an overview over the most important string functions.

Prefix Classes

Assume that we want to extend our shape system. From time to time, we need shapes that behave exactly like the ones above, but are tied to a fixed position. That is, they do not change their position even if they receive a move> message.

One solution would be to modify the move> method in the +Shape class to a no-operation. But this would require to duplicate the whole shape hierarchy (e.g. by defining +FixedShape, +FixedRectangle and so on).

The PicoLisp Way is the use of Prefix Classes through multiple inheritance. It uses the fact that searching for method definitions is a depth-first, left-to-right search of the class tree. We define a prefix class:

: (class +Fixed)
(dm move> (DX DY))  # A do-nothing method

Now let's define a Fixed Rectangle by adding the prefix class when we create the object:

: (setq R (new '(+Fixed +Rectangle) 0 0 30 20))    # '+Fixed' prefix class
-> $134432881

Now the rectangle will not move even if we apply the move> function:

: (move> R 10 5)                                   # Send 'move>' message
-> NIL

: (show R)
$134432881 (+Fixed +Rectangle)
   dy 20
   dx 30
   y 0                                             # Did not move!
   x 0

Alternatively, it's also possible to define a new subclass +FixRect that inherits from both +Fixed and +Rectangle:

: (class +FixRect +Fixed +Rectangle)
-> +FixRect

and then use it directly:

: (setq R (new '(+FixRect) 0 0 30 20))
-> $13455710

Internal Representation

In the PicoLisp Explored-series we always try to cover the internal representation of the data as well.

As you might know by now, PicoLisp has only three data types: numbers, symbols and lists. Where do we find objects and classes in this model? Do we need to change it?

Of course not - objects as well as classes are both implemented as symbols. In fact, there is no formal difference between objects and classes; classes are more a conceptual design consideration in the head of the programmer than a physical reality.

We will find many examples for class concepts in the next posts of the Web Application Tutorial.