Rosetta Code: Object Oriented Programming Examples

miatemma

Mia

Posted on February 4, 2022

Rosetta Code: Object Oriented Programming Examples

Before we return to the "Web Application Programming" tutorial, let's take a look at the Rosetta Code tasks about Object Oriented Programming, which covers a number of typical tasks and concepts of object-oriented programming.

For the general concept of OOP in PicoLisp, read here:


The Rosetta Code has 18 tasks in total related to Object Oriented Programming. All of them have a PicoLisp solution. You can find them under this link.

oop-rosetta.png

We will quickly go through the examples in alphabetic order.


Abstract Type

In OOP, abstract types are types that cannot be instantiated directly. Nevertheless they serve as superclasses for concrete implementations.

In PicoLisp, there is no formal difference between abstract and concrete classes. However, there is a naming convention that abstract classes should start with a lower-case character after the +: This tells the programmer that this class has not enough methods defined to survive on its own.

(class +abstractClass)

(dm someMethod> () 
   (foo)
   (bar) )
Enter fullscreen mode Exit fullscreen mode

Active Object

In object-oriented programming an object is active when its state depends on clock. Usually an active object encapsulates a task that updates the object's state. To the outer world the object looks like a normal object with methods that can be called from outside. Implementation of such methods must have a certain synchronization mechanism with the encapsulated task in order to prevent object's state corruption.

This task is rather complex and too long to post it here. We might go through it in a separate post later. You can see the PicoLisp solution here.


Add a variable to a class instance at runtime

Demonstrate how to dynamically add variables to an object (a class instance) at runtime.

In general, all instance variables in PicoLisp are dynamically created at runtime. We can put a new variable using the put function:

: (setq MyObject (new '(+MyClass)))       # Create some object
-> $385605941

: (put MyObject 'newvar '(some value))    # Set variable
-> (some value)
: (show MyObject)                         # Show the object
$385605941 (+MyClass)
   newvar (some value)
-> $385605941
Enter fullscreen mode Exit fullscreen mode

Break OO Privacy

Show how to access private or protected members of a class in an object-oriented language from outside an instance of the class, without calling non-private or non-protected members of the class as a proxy. The intent is to show how a debugger, serializer, or other meta-programming tool might access information that is barred by normal access methods.

This is realized in PicoLisp using transient symbols. Since we haven't covered the difference between internal, transient and external symbols yet, let's skip this task. If you're interested, check the solution here.


Call an object method

Show how to call a static or class method, and an instance method of a class.

Easy one, we know this already:

(foo> MyClass)
(foo> MyObject)
Enter fullscreen mode Exit fullscreen mode

Classes

Create a basic class with a method, a constructor, an instance variable and how to instantiate it.

Easy one too!

(class +Rectangle)
# dx dy

(dm area> ()  # Define a a method that calculates the rectangle's area
   (* (: dx) (: dy)) )

(println  # Create a rectangle, and print its area
   (area> (new '(+Rectangle) 'dx 3 'dy 4)) )
Enter fullscreen mode Exit fullscreen mode

Constrained genericity

Say a type is called "eatable" if you can call the function eat on it. Write a generic type FoodBox which contains a collection of objects of a type given as parameter, but can only be instantiated on eatable types. The FoodBox shall not use the function eat in any way.

First we define a class +Eatable with the method eat:

(class +Eatable)

(dm eat> ()
   (prinl "I'm eatable") )
Enter fullscreen mode Exit fullscreen mode

Then we define the class +FoodBox. We can place properties into the FoodBox using set>, but only, if this property (object) has the method eat>.

(class +FoodBox)
# obj

(dm set> (Obj)
   (unless (method 'eat> Obj)                # Check if the object is eatable
      (quit "Object is not eatable" Obj) )
   (=: obj Obj) )                            # If so, set the object

Enter fullscreen mode Exit fullscreen mode

Let's try to place an eatable object inside, and a non-eatable (random) object too. The latter one will return an error.

(let (Box (new '(+FoodBox))  Eat (new '(+Eatable))  NoEat (new '(+Bla)))
   (set> Box Eat)       # Works
   (set> Box NoEat) )   # Gives an error
Enter fullscreen mode Exit fullscreen mode

Delegates

A delegate is a helper object used by another object. The delegator may send the delegate certain messages, and provide a default implementation when there is no delegate or the delegate does not respond to a message.

First, we define a class Delegator with a method operation> and a property delegate. If delegate is defined, the method delegate calls the method thing in the delegate object, otherwise it returns "default implementation".

(class +Delegator)
# delegate

(dm operation> ()
   (if (: delegate)
      (thing> @)
      "default implementation" ) )
Enter fullscreen mode Exit fullscreen mode

Next, we define the class +Delegate with a message thing>.

(class +Delegate)
# thing

(dm T (Msg)
   (=: thing Msg) )

(dm thing> ()
   (: thing) )
Enter fullscreen mode Exit fullscreen mode

Now let's test it:

(let A (new '(+Delegator))
   # Without a delegate
   (println (operation> A))
Enter fullscreen mode Exit fullscreen mode

returns "Default Implementation",

   # With delegate that does not implement 'thing>'
   (put A 'delegate (new '(+Delegate)))
   (println (operation> A))
Enter fullscreen mode Exit fullscreen mode

returns NIL, and

# With delegate that implements 'thing>'
   (put A 'delegate (new '(+Delegate) "delegate implementation"))
   (println (operation> A)) )
Enter fullscreen mode Exit fullscreen mode

returns the delegated message: "delegate implementation".


Inheritance/Multiple

Write two classes (or interfaces) Camera and MobilePhone, then write a class CameraPhone which is both a Camera and a MobilePhone.

 (class +Camera)
 (class +MobilePhone)
 (class +CameraPhone +Camera +MobilePhone)
Enter fullscreen mode Exit fullscreen mode

Inheritance/Single

Show a tree of types which inherit from each other. The Tree should look like this:

                        Animal
                          /\
                         /  \
                        /    \
                      Dog    Cat
                      /\
                     /  \
                    /    \
                  Lab  Collie

Solution:

(class +Animal)
(class +Dog +Animal)
(class +Cat +Animal)
(class +Lab +Dog)
(class +Collie +Dog)
Enter fullscreen mode Exit fullscreen mode

Show dependencies using the dep function:

: (dep '+Animal)
+Animal
   +Cat
   +Dog
      +Collie
      +Lab
Enter fullscreen mode Exit fullscreen mode

Object Serialization

Create a set of data types based upon inheritance. Each data type or class should have a print command that displays the contents of an instance of that class to standard output. Create instances of each class in your inheritance hierarchy and display them to standard output.

Write each of the objects to a file named objects.dat in binary form using serialization or marshalling. Read the file


 and print the contents of each serialized object. 

Let's create two classes, +Point and its subclass +Circle. Both have a print> method.

(class +Point)
# x y

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

(dm print> ()
   (prinl "Point " (: x) "," (: y)) )
Enter fullscreen mode Exit fullscreen mode
(class +Circle +Point)
# r

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

(dm print> ()
   (prinl "Circle " (: x) "," (: y) "," (: r)) )

Enter fullscreen mode Exit fullscreen mode

Create objects of each class and print them to standard output:

(setq
   P (new '(+Point) 3 4)
   C (new '(+Circle) 10 10 5) )

(print> P)
(print> C)

# Output:
# Point 3,4
# Circle 10,10,5
Enter fullscreen mode Exit fullscreen mode

Now let's write it to a filme object.dat using the pr function which serializes any kind of data:

(out "objects.dat"
   (pr (val P) (getl P))
   (pr (val C) (getl C)) )
Enter fullscreen mode Exit fullscreen mode

and now let's read it back using the (rd) function:

(in "objects.dat"
   (putl (setq A (box (rd))) (rd))
   (putl (setq B (box (rd))) (rd)) )
Enter fullscreen mode Exit fullscreen mode

Polymorphic Copy

An object is polymorphic when its specific type may vary. The types a specific value may take, is called class.

The task: let a polymorphic object contain an instance of some specific type S derived from a type T. The type T is known. The type S is possibly unknown until run time. The objective is to create an exact copy of such polymorphic object (not to create a reference, nor a pointer to).

PicoLisp: Any object can be copied by transferring the value and the property list. Create an object A and copy it to a new object B:

: (setq A (new '(+Cls1 +Cls2) 'attr1 123  'attr2 "def"  'attr3 (4 2 0)  'attr4 T))
-> $385603635

: (putl (setq B (new (val A))) (getl A))
Enter fullscreen mode Exit fullscreen mode

Then (show A) and (show B) will both return the same output:

$385346595 (+Cls1 +Cls2)
   attr1 123
   attr2 "def"
   attr3 (4 2 0)
   attr4
Enter fullscreen mode Exit fullscreen mode

Polymorphism

Create two classes Point(x,y) and Circle(x,y,r) with a polymorphic function print, accessors for (x,y,r), copy constructor, assignment and destructor and every possible default constructors.

Let's define a class +Point and a subclass +Circle each with a function print> and the initializing function T:

(class +Point)
# x y

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

(dm print> ()
   (prinl "Point " (: x) "," (: y)) )
Enter fullscreen mode Exit fullscreen mode
(class +Circle +Point)
# r

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

(dm print> ()
   (prinl "Circle " (: x) "," (: y) "," (: r)) )
Enter fullscreen mode Exit fullscreen mode

Now the output of print> returns different results depending on the object class:

(setq
   P (new '(+Point) 3 4)
   C (new '(+Circle) 10 10 5) )

(print> P)
(print> C)
Enter fullscreen mode Exit fullscreen mode

returns:

Point 3,4
Circle 10,10,5
Enter fullscreen mode Exit fullscreen mode

Reflection/List methods

The goal is to get the methods of an object, as names, values or both.

This is possible in debug mode using the function methods. Let's open our shape.l-file with the +Rectangle-class definition from the previous post (or from here) and open it with $ pil shape.l +.

After creating a rectangle object R, we can check its methods using (method R). For each method, we can see from which class it is inherited.

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

: (methods R)
-> ((draw> . +Rectangle) (perimeter> . +Rectangle) (area> . +Rectangle) (T . +Rectangle) (move> . +Shape))
Enter fullscreen mode Exit fullscreen mode

Reflection/List properties

The goal is to get the properties of an object, as names, values or both.

We re-use the example from above and can check the properties of R using show (unlike the previous example, this is not restricted to debugger).

: (show R)
$177715702441044 (+Rectangle)
   dy 20
   dx 30
   y 0
   x 0
-> $177715702441044
Enter fullscreen mode Exit fullscreen mode

Respond to an unknown method call

Demonstrate how to make the object respond (sensibly/usefully) to an invocation of a method on it that it does not support through its class definitions.

The function try is used to send a message to an object for which it is not known whether it inherits a method for that message or not. As opposed to the syntacically equivalent send function, try does not give an error, but returns NIL.

: (try 'msg> 123)
-> NIL
: (try 'html> 'a)
-> NIL
Enter fullscreen mode Exit fullscreen mode

Send an unknown method call

Invoke an object method where the name of the method to be invoked can be generated at run time.

This can be done using the send function. If the function cannot be located in the object's classes and superclasses, an error "Bad message" is issued.

# (send (expression) Obj arg1 arg2)
: (send 'stop> Dlg)
-> NIL
Enter fullscreen mode Exit fullscreen mode

Singleton

A Global Singleton is a class of which only one instance exists within a program.

As there is no physical difference between classes and objects, we can use the class symbol itself (instead of instantiating an object).

(class +Singleton)

(dm message1> ()
   (prinl "This is method 1 on " This) )

(dm message2> ()
   (prinl "This is method 2 on " This) )
Enter fullscreen mode Exit fullscreen mode

Output:

: (message1> '+Singleton)
This is method 1 on +Singleton
-> +Singleton

: (message2> '+Singleton)
This is method 2 on +Singleton
-> +Singleton
Enter fullscreen mode Exit fullscreen mode

That's it! Now that we have covered the most important aspects of object-oriented programming in PicoLisp, let's return to our Web Application Programming tutorial and see how we can use our new knowledge.


Sources

💖 💪 🙅 🚩
miatemma
Mia

Posted on February 4, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related