Scope Oriented Programming

There is a long discussion talking about the advantages of Procedural Programming vs. the advantages of Object Oriented Programming. In previous posts I tried to show that although OOP is newer it is not superior. The posts were Flaws of Object Oriented Modeling and Flaws of Object Oriented Modeling Continue. This time I want to present a new programming model which takes advantage of both methods and in some way corresponds to Why you should use Procedural and OOP in every application.

Let's start with the problems:

1. Procedural Programming does not enforce scopes and therefore there are too many global variables and objects. This means that it is almost impossible to manage huge projects.
2. Object Oriented Programming usually implements many small fragments of code which call and utilize other small fragments of code. In the parallel world, if you have a race condition it is almost impossible to track the flow of execution because going step-by-step may mean stepping over multiples of 10 of function calls before a single task is complete.
3. Nobody is protecting us in either case from using an object after it is deleted or closed, or before it was initialized. In the parallel world this will often cause problems.
4. A very powerful technique is called 'smart pointers' which are pointers that automatically free up resources when no one needs them anymore. In managed languages like C# and Java this means when no one is referencing the object. In C++ this means when the execution leaves a (stack) scope { }. Both Procedural and OOP models do not utilize this concept fully and programmers often need to manually mark resources as free.

Now that we have the new language extensions (C# TPL and C++ 0x) we can write our code in scopes.

For simplicity and readability I'll use C# for the sample code.
Here is an example of a base class that we might use today:

public abstract class Old_Stream
{
   public abstract void Open();
   public abstract void Close();
   public abstract byte Read();
   public abstract void Write(byte value);
}

This simple stream has 4 methods which we are expected to override on another class.
Here is that class:

public

class Old_MemoryStream : Old_Stream
{
   byte[] buffer;
   int indexR;
   int indexW;
   public override void Open() { buffer = new byte[1024]; indexR=-1; indexW=-1; }
   public override void Close() { buffer = null; }
   public override byte Read() { indexR = ((indexR + 1) % buffer.Length); return (buffer[indexR]); }
   public override void Write(byte value) { indexW = ((indexW + 1) % buffer.Length); buffer[indexW] = value; }
}

Now let's see the new version of the same base class. Here is the first part:

public abstract class New_Stream
{
   protected delegate void CloseProcType();
   protected delegate byte ReadProcType();
   protected delegate void WriteProcType(byte value);

The first part defines (typedef style) types of callback functions.

   protected CloseProcType CloseFunction;
   protected ReadProcType ReadFunction;
   protected WriteProcType WriteFunction;

The second part has the implementation of the callbacks (function-pointer style), and the third part is the signature of the class just like we had with the old base class:

   public abstract void Open();
   public void Close() { CloseFunction(); }
   public byte Read() { return (ReadFunction()); }
   public void Write(byte value) { WriteFunction(value); }
}

The difference in this base class signature is that here only Open() is virtual and we can only override Open() in derived classes. All the other methods are simply wrappers for the function callbacks.

This is of course all just the framework. The real revolution is here, in the derived class:

public class New_MemoryStream : New_Stream
{
   byte[] buffer;
   int indexR;
   int indexW;

The implementation starts exactly like the older implementation of a derived class, same as what we have above.
The following in the implementation only for the Open() function:

   public override void Open()
   {
      buffer = new byte[1024]; indexR = -1; indexW = -1;

      ReadFunction = (() =>
      {
         indexR = ((indexR + 1) % buffer.Length);
         return (buffer[indexR]);
      });

      WriteFunction = ((value) =>
      {
         indexW = ((indexW + 1) % buffer.Length);
         buffer[indexW] = value;
      });

      CloseFunction = (() =>
      {
         buffer = null;
         ReadFunction = null;
         WriteFunction = null;
         CloseFunction = null;
      });
   }

So what just happened?
I implemented the Read, Write, and Close functions according to their signatures using Anonymous Methods. These are functions that have no name. The compiler enforces parameters and return values based on the function's signature, so for example "CloseFunction" will enforce no parameters and no return value upon the Anonymous Method. This is superior to just using a typeless Anonymous Method as we often see today.

What is that good for? Well, first of all you cannot use Read and Write before Open was called or after Close was called. This means that the compiler is enforcing that we can call Read() only after Open() has initialized all the members. The reason is that ReadFunction is not initialized until after all the value members were initialized.

What actually happens when we call open is that all the value members are initialized on the first line. Then the three function callbacks are initialized to point to their Anonymous Methods. Nothing else happens all the rest of the code is part of the Anonymous Methods and is therefore not executed as part of the call to Open().

A real world scenario is too long and complex to post on a blog but I can do better that what we have above.
Here is an improved scenario, just so we can get a feel of what's really hiding behind this programming model:

public class New_MemoryStream2 : New_Stream
{
   public override void Open()
   {
      byte[] buffer = new byte[1024];
      int indexR = -1;
      int indexW = -1;

      ReadFunction = (() =>
      {
         indexR = ((indexR + 1) % buffer.Length);
         return (buffer[indexR]);
      });

      WriteFunction = ((value) =>
      {
         indexW = ((indexW + 1) % buffer.Length);
         buffer[indexW] = value;
      });

      CloseFunction = (() =>
      {
         buffer = null;
         ReadFunction = null;
         WriteFunction = null;
         CloseFunction = null;
      });
   }
}

This is an improved implementation of the derived class. It has only one member method Open() and nothing else. All the value members are now inside the function Open(). This means that the value members exist inside the scope of Open().
What else is inside Open()?
- The Anonymous Methods.
This means that the Anonymous Method for Read() can see the local variables. We can add another Sub-Scope inside Read, for example to run in parallel using another thread. A simple scenario for another internal Scope is a thread-pool thread which is waiting for some operation to complete (for example log write in parallel to the read operation).

Open has executed once and completed so it is no longer part of the game. When the user of this derived class calls Close() all methods are released (according to our code). At this point there is no method to use the buffer which was allocated when Open() was executed, and only now the buffer is released. If for example we did not set "WriteFunction = null" inside of Close() then the buffer would still be usable until we disposed of the class's instance.

This means that we can allocate the object once and then call Open() and Close() many times. Class members will now behave like static and will out-live Close(). This is a superior implementation to using static members which are actually global to the application and usually cause problems when we start deriving classes.

This method was useful for me a couple of years ago when I implemented an automated test system. Instead of deriving and overriding functions I used a factory to assign the methods. This enabled the tested module to come with its built-in tests. In the scenario above we could for example have an implementation for OpenTextFile(), OpenUDPPort(), and OpenHTTP() as extension methods, each using a different type of Read and Write API internally.

As I said above, it is hard to demonstrate a real-world scenario in a short blog post so I can only hope that you can share my vision.

For more complete information about compiler optimizations, see our Optimization Notice.