Software Development - Functional Programming - RoboProg's

RoboProg's / Software Development

Last Month


Jun 2, 2010

OOP via functions

What would you do if you were "stranded" on a desert island and your only programming tool was a functional language, instead of more conventional object oriented language? Assuming the loss of OOP was indeed a problem, you would emulate OOP structure, easily, in a functional language, of course. Let's see one way you might do it.

First, in order to present an example, we are going to need a functional programming language. Ideally, it should be one that many of us are somewhat familiar with. That said, may I present:

JavaScript, The Functional Parts.

Javascript does most of what a "real" functional language does: functions are "first class" -- you can assign one to a reference variable like any other value; functions can be nested (which is implied by the next statement); functions can be used as closures -- maintaining references to variables from enclosing scopes (which allows you to do "currying", among other tricks).

So what is missing? One big thing is that most implementations do not optomize tail recursion. Also, javascript has no concept of immutable objects or "assign-only-once" symbols (many may not mourn the loss of assign-only-once, but it can have its place).

For the sake of the next exersize, let's pretend that JavaScript is a true functional (only) language, rather than an OOP/functional hybrid. That is, let's ignore all of the dot/pseudo-associative-array notation that lets an outer scope examine the nested variables/fields of a value.

The following is one way you might go about simulating OOP structures using closures:

CodeNotes

// "constructor":  initialize instance state and provide method dispatch table
function new_person( in_name, in_gender, in_father, in_mother)
	{
	var name, gender, father, mother

	// initialize "instance" data:
	//  ("curry" paramters from outermost function,
	//  freezing them on this instance)

	name = in_name
	gender = in_gender
	father = in_father
	mother = in_mother

	

Simulate a constructor by creating a closure to contain the instance variables for each invocation. The function new_person is acting as a closure by creating variables which are used by its own nested functions.

If we had needed to emulate static data, we could have made an outermost function to serve as a context for the static data. This outer function would have returned the constructor function for the individual instances. This nested constructor would then itself be a closure with access to the static data for the class.


	// emulate an object by making a function that returns other functions
	return function( method_name)
		{
		// provide "method table":

		return ( method_name === 'get_name') ?
			// return the simple name of this person
			function ()
				{ return name }
			: ( method_name === 'get_geneology') ?
			// return the lineage of this person
			function ()
				{
				// TODO:  support non-sexist/linear geneology  :-)
				return name +
					( ! father ?
						''
						: ', ' +
							( gender === 'F' ? 'daughter' : 'son') +
							' of ' + father( 'get_geneology')()
					)
				}  // ______
	

Marry some code to the object as well as the data above.

Certainly there is room to optomize this implementation of a method / message-dispatch table, but I am trying to stick with the most primitive implementaion, assuming little available in any given (functional) language.

Here, we return functions to handle the messages get_name and get_geneology.


			: ( method_name === 'be_adopted_by') ?
			// return a new instance of "this" person with a new parent
			function ( new_father, new_mother)
				{
				return new_person( name, gender,
					new_father ? new_father : father,
					new_mother ? new_mother : mother)
				}  // ______
			: undefined
		}  // ______

	}  // ______

	

Implement a pseudo-mutator (be_adopted_by) which constructs a new instance based on the old instance plus the desired changes. Assuming immutable data, this is how it has to work (construct a mutated clone).

This method will be used (sort of) in the second example.


// demo simple use of "objects"
function demo_simple()
	{
	var adam, eve, abel

	adam = new_person( 'Adam', 'M')
	eve = new_person( 'Eve', 'F')
	abel = new_person( 'Abel', 'M', adam, eve)
	alert( 'Created instance for ' + abel( 'get_geneology')() )
	}  // ______
	

Click the button to run the demo:


// subclass constructor:  mutant-is-a-person
function new_mutant( in_name, in_gender,
		in_power, in_alias, in_father, in_mother)
	{
	// emulate a subclass by again making a function
	//  that returns other functions, and reusing the base class.
	return function( method_name)
		{
		var base, power, alias

		// initialize super-class:
		base = new_person( in_name, in_gender, in_father, in_mother)

		// initialize "instance" data:

		power = in_power
		alias = in_alias

		// provide extended "method table":

		return ( method_name === 'get_power') ?
			// return the power of this mutant
			function ()
				{ return power }
			: ( method_name === 'get_alias') ?
			// return the "code name" of this mutant
			function ()
				{ return alias }
			// override original method as needed here:
			: ( method_name === 'be_adopted_by') ?
			// return a new instance of "this" person with a new parent
			function ( new_father, new_mother)
				{
				return new_mutant( in_name, in_gender, power, alias,
					new_father ? new_father : in_father,
					new_mother ? new_mother : in_mother)
				}  // ______
			// return inherited methods:
			: base( method_name)
		}  // ______

	}  // ______

	

Here is a way to emulate subclassing.

Once again, we use a closure to contain the instance data. We also save a reference to the superclass instance, so we can reuse its message handlers, er, methods.

In keeping with our message based method calling, we are also effectively using prototyping for inheritance (as opposed to compile time static method table setup).


// demo more complex use of "objects"
function demo_complex()
	{
	var orig, piedro

	orig = new_mutant( 'Piedro', 'M', 'Speed', 'Quicksilver')

	// OK, abuse "adoption" for discovery of family secrets  :-)
	piedro = orig( 'be_adopted_by')(
			new_mutant( 'Magnus', 'M', 'Magnetism', 'Magneto') )
	alert( 'Created instance for "' + piedro( 'get_geneology')() +
			'" AKA "' + piedro( 'get_alias')() +
			'", power: ' + piedro( 'get_power')() )
	}  // ______
	

Click the button to run the demo:

So far, so good. We can use a function that returns other functions as a message handler (a close approximation of a virtual method table). We can use closures to capture instance data.

Finally, a critique of the example: it would be nice to save a reference to the base class constructor to aid in cloning an immutable object; it would be nice have access levels on the various methods to limit the scope in which they can be used; it is of course not as fast as C++ as implemented (not that I particularly like C++). I'm sure you can think of other improvements, as well.


(yeah, it's been a while)


Contact me:
r
o
b
o
p
r
o
g
@
yahoo.com

Copyright 2010, Robin R Anderson