prototype

prototype -- Prototype Based OO Programming For Lua

Introduction · Basic Usage · Reference · Download · Installation · Contact · License

Introduction

There are basically two ways of doing object oriented programming: class-based and prototype-based. While class-based OO is certainly easier to implement for compiler-writers and therefore much more familiar for most programmers (thanks to C++, Java, et al.), prototype-based OO is actually easier (half the number of concepts and less special cases) and fits nicely to dynamic interpreted languages like Lua. This wikipage briefly handles prototype-based OO, but fails to address the biggest problem: How to clone objects ...

Basic Usage

The prototype module can create root objects for prototype-based OO hierarchies:

$ cat > test.lua
local prototype = require( "prototype" )
local object = prototype{
  default = prototype.assignment_copy,
}
local person = object:clone()
person.name = "anonymous"
function person:say()
  print( "Hi, I'm " .. self.name )
end
person:say()
local alice = person:clone()
alice.name = "Alice"
alice:say()
^D

The output is (as expected):

$ lua test.lua
Hi, I'm anonymous
Hi, I'm Alice

Reference

The prototype module is a functable which serves as a generator for custom tailored prototype root objects and as a container for some predefined cloning policies. You call the prototype module passing a configuration table as argument, which configures the default and per-type cloning policies. The configuration table can have the following fields:

Calling the prototype module returns a root object, which provides the following three methods:

Cloning Policies

Cloning is easy for value types like numbers and booleans, and for constant reference types like strings. But non-constant reference types like tables or even userdata are more of a problem. E.g. for an array or a map-like data structure you typically want the table itself copied (not just the reference put into the cloned object), but the keys and values should refer to the old values, so that you can add or remove elements from those data structures without affecting the parent object but lookup the original values. For graph-like data structures you want a deep copy which also handles cycles, etc. And all bets are off for userdata or objects with metatables.

This module allows you to specify default, per-type, and per-slot cloning policies to handle all cloning needs for your data. You can define your own cloning policies (functions taking one argument and returning its copy) or use one of the predefined functions in the prototype module:

Examples

Use delegation for almost everything (and shallow copy for tables):

local object = prototype{
  default = prototype.no_copy,
  table = prototype.shallow_copy,
  use_prototype_delegation = true,
}

Use assignment copy (and shallow copy for tables), no delegation or index lookup:

local object = prototype{
  default = prototype.assignment_copy,
  table = prototype.shallow_copy,
}

Explicitly declare all slots, protect objects from undeclared slots, use no default cloning policy:

local object = prototype{
  -- should never happen:
  default = function() error( "Argh, unknown slot!" ) end,
  use_prototype_delegation = true,
  use_slot_protection = true,
  use_clone_delegation = true,
}
object:slot( "val", prototype.assignment_copy )
object.val = 1

Use assignment for value data, delegation for references, and shallow copy for tables (and don't put __index or __newindex in the objects):

local object = prototype{
  default = prototype.no_copy,
  boolean = prototype.assignment_copy,
  number = prototype.assignment_copy,
  string = prototype.assignment_copy,
  table = prototype.shallow_copy,
  use_prototype_delegation = true,
  use_extra_meta = true,
}

Adding Mixins to Prototypes

There are cases where the desired functionality can only be implemented using external C modules or where preexisting Lua objects are available that should be integrated into the prototype hierarchy.

The good news is that the mixin-method can add delegation methods to a prototype object which call the external C or Lua object. The downside is that these external C or Lua objects must be cloneable (i.e. they must provide a clone-method that returns a copy of its single argument). Prototype objects provide such a method, so you can use prototype objects as mixins in other prototype objects.

Basic usage (but probably using the C API) is:

$ cat > test.lua
local u = newproxy( true )
local mt = getmetatable( u )
mt.__index = {
  peek = function( self )
    print( "peeking:", self )
  end,
  clone = function( self )
    print( "cloning:", self )
    return newproxy( self )
  end
}
local prototype = require( "prototype" )
local object = prototype{
  default = prototype.no_copy,
  use_prototype_delegation = true,
}
object:mixin( u, "peek" )
local cloned = object:clone()
cloned:peek()
^D

The result is:

$ lua test.lua
cloning:        userdata: 0x8052a4c
peeking:        userdata: 0x805418c

(newproxy is an undocumented function included in Lua up to version 5.1, that creates a zero-size userdata.)

Download

The source code (with documentation and test scripts) is available on github.

Installation

There are two ways to install this module, either using luarocks (if this module already ended up in the main luarocks repository) or manually.

Using luarocks, simply type:

luarocks install prototype

To install the module manually just drop prototype.lua somewhere into your Lua package.path.

Contact

Philipp Janda, siffiejoe(a)gmx.net

Comments and feedback are always welcome.

License

prototype is copyrighted free software distributed under the MIT license (the same license as Lua 5.1). The full license text follows:

prototype (c) 2013 Philipp Janda

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHOR OR COPYRIGHT HOLDER BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.