Documentation ¶
Overview ¶
Package goop (Go Object-Oriented Programming) provides support for dynamic object-oriented programming constructs in Go, much like those that appear in various scripting languages. The goal is to integrate fast, native-Go objects and slower but more flexible Goop objects within the same program.
FEATURES: For flexibility, Goop uses a prototype-based object model (cf. http://en.wikipedia.org/wiki/Prototype-based_programming) rather than a class-based object model. Objects can be created either by inheriting from existing objects or from scratch. Data fields (a.k.a. properties) and method functions can be added and removed at will. Multiple inheritance is supported. An object's inheritance hierarchy can be altered dynamically. Methods can utilize type-dependent dispatch (i.e., multiple methods with the same name but different argument types).
As an example, let's create an object from scratch:
pointObj := goop.New()
Now let's add a couple of data fields to pointObj:
pointObj.Set("x", 0) pointObj.Set("y", 0)
Unlike native Go, Goop lets you define multiple method functions with the same name, as long as the arguments differ in type and/or number:
pointObj.Set("moveBy", goop.CombineFunctions( func(this goop.Object, xDelta, yDelta int) { this.Set("x", this.Get("x") + xDelta) this.Set("y", this.Get("y") + yDelta) }, func(this goop.Object, delta int) { this.Set("x", this.Get("x") + delta) this.Set("y", this.Get("y") + delta) }))
Admittedly, having to use Get and Set all the time can be a bit tedious. Functions that are less trivial than the above will typically call Get and Set only at the beginning and end of the function and use local variables for most of the computation.
Use Call to call a method on an object:
pointObj.Call("moveBy", 3, 5) pointObj.Call("moveBy", 12)
Call returns all of the method's return values as a single slice. Use type assertions to put the individual return values into their correct format:
pointObj.Set("distance", func(this goop.Object) float64 { x := float64(this.Get("x")) y := float64(this.Get("y")) return math.Sqrt(x*x + y*y) }) d := pointObj.Call("distance")[0].(float64)
Again, sorry for the bloat, but that's what it takes to provide this sort of dynamic behavior in Go.
The following more extended example shows how to define and instantiate an LCMCalculator object, which is constructed from two integers and provides methods that return the greatest common divisor and least common multiple of those two numbers. Each of those methods memoizes its return value by redefining itself after its first invocation to a function that returns a constant value.
// This file showcases the Goop package by reimplementing the JavaScript LCM example from // http://en.wikipedia.org/wiki/Javascript#More_advanced_example. package main import "github.com/lanl/goop" import "fmt" import "sort" // Finds the lowest common multiple of two numbers func LCMCalculator(this goop.Object, x, y int) { // constructor function this.Set("a", x) this.Set("b", y) this.Set("gcd", func(this goop.Object) int { // method that calculates the greatest common divisor abs := func(x int) int { if x < 0 { x = -x } return x } a := abs(this.Get("a").(int)) b := abs(this.Get("b").(int)) if a < b { // swap variables a, b = b, a } for b != 0 { t := b b = a % b a = t } // Only need to calculate GCD once, so "redefine" this // method. (Actually not redefinition - it's defined // on the instance itself, so that this.gcd refers to // this "redefinition".) this.Set("gcd", func(this goop.Object) int { return a }) return a }) this.Set("lcm", func(this goop.Object) int { lcm := this.Get("a").(int) / this.Call("gcd")[0].(int) * this.Get("b").(int) // Only need to calculate lcm once, so "redefine" this method. this.Set("lcm", func(this goop.Object) int { return lcm }) return lcm }) this.Set("toString", func(this goop.Object) string { return fmt.Sprintf("LCMCalculator: a = %d, b = %d", this.Get("a").(int), this.Get("b").(int)) }) } type lcmObjectVector []goop.Object func (lov lcmObjectVector) Less(i, j int) bool { a := lov[i].Call("lcm")[0].(int) b := lov[j].Call("lcm")[0].(int) return a < b } func (lov lcmObjectVector) Len() int { return len(lov) } func (lov lcmObjectVector) Swap(i, j int) { lov[i], lov[j] = lov[j], lov[i] } func main() { var lcmObjs lcmObjectVector for _, d := range [][]int{{25, 55}, {21, 56}, {22, 58}, {28, 56}} { lcmObjs = append(lcmObjs, goop.New(LCMCalculator, d[0], d[1])) } sort.Sort(lcmObjs) for _, lcm := range lcmObjs { fmt.Printf("%s, gcd = %d, lcm = %d\n", lcm.Call("toString")[0], lcm.Call("gcd")[0], lcm.Call("lcm")[0]) } }
Index ¶
- Variables
- type MetaFunction
- type Object
- func (obj *Object) Call(methodName string, arguments ...interface{}) []interface{}
- func (obj *Object) Contents(alsoMethods bool) map[string]interface{}
- func (obj *Object) Get(memberName string) (value interface{})
- func (obj *Object) IsEquiv(otherObj Object) bool
- func (obj *Object) Set(memberName string, value interface{})
- func (obj *Object) SetSuper(parentObjs ...interface{})
- func (obj *Object) Super() []Object
- func (obj *Object) Unset(memberName string)
Constants ¶
This section is empty.
Variables ¶
var ErrNotFound = errors.New("Member not found")
ErrNotFound is returned by a failed attempt to locate an object member.
Functions ¶
This section is empty.
Types ¶
type MetaFunction ¶
type MetaFunction func(varArgs ...interface{}) (funcResult []interface{})
A MetaFunction encapsulates one or more functions, each with a unique argument-type signature. When a MetaFunction is invoked, it accepts arbitrary inputs and returns arbitrary outputs (bundled into a slice). On failure to find a matching signature, a singleton slice containing ErrNotFound is returned.
func CombineFunctions ¶
func CombineFunctions(functions ...interface{}) MetaFunction
CombineFunctions combines multiple functions into a single MetaFunction for type-dependent dispatch.
type Object ¶
type Object struct {
Implementation *internal // Internal representation not exposed to the user
}
Object is a lot like a JavaScript object in that it uses prototype-based inheritance instead of a class hierarchy.
func New ¶
func New(constructor ...interface{}) Object
New allocates and return a new object. It takes as arguments an optional constructor function with optional arguments.
func (*Object) Call ¶
Call invokes a method on an object and returns the method's return values as a slice. Call returns a slice of the singleton ErrNotFound if the method could not be found.
func (*Object) Contents ¶
Contents returns a map of all members of an object (useful for iteration). If the argument is true, Contents also includes method functions.
func (*Object) IsEquiv ¶
IsEquiv returns whether another object is equivalent to the object in question.
func (*Object) SetSuper ¶
func (obj *Object) SetSuper(parentObjs ...interface{})
SetSuper specifies the object's parent object(s). This is the mechanism by which both single and multiple inheritance are implemented. For convenience, parents can be specified either individually or as a slice.