goop

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2022 License: BSD-3-Clause Imports: 2 Imported by: 0

README

Goop

Go Reference Go Report Card

Description

The Goop (Go Object-Oriented Programming) package 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

Goop provides the following features, which are borrowed from an assortment of object-oriented programming languages:

  • a prototype-based object model

  • support for both ex nihilo and constructor-based object creation

  • the ability to add, replace, and remove data fields and method functions at will

  • multiple inheritance

  • dynamically modifiable inheritance hierarchies (even on a per-object basis)

  • type-dependent dispatch (i.e., multiple methods with the same name but different argument types)

Installation

Goop is a Go module and therefore does not need to be installed manually. Simply import it as github.com/lanl/goop, and go build should download and install the code automatically.

Documentation

Pre-built documentation for the Goop API is available online at https://pkg.go.dev/github.com/lanl/goop.

Performance

Goop is unfortunately extremely slow. Goop programs have to pay for their flexibility in terms of performance. To determine just how bad the performance is on your computer, you can run the microbenchmarks included in goop_test.go:

    go test -bench=. -benchtime=5s github.com/lanl/goop

On my computer, I get results like the following (reformatted for clarity):

    BenchmarkNativeFNV1             2000000000                 4.59 ns/op
    BenchmarkNativeFNV1Closure      2000000000                 4.59 ns/op
    BenchmarkGoopFNV1                100000000                70.5  ns/op
    BenchmarkMoreGoopFNV1             10000000               794    ns/op
    BenchmarkEvenMoreGoopFNV1          5000000              2517    ns/op

See goop_test.go for the complete source code for those benchmarks. Basically,

  • BenchmarkNativeFNV1 is native (i.e., non-Goop) Go code for computing a 64-bit FNV-1 hash on a sequence of 0xFF bytes. Each iteration ("op" in the performance results) comprises a nullary function call, a multiplication by a large prime number, and an exclusive or with an 0xFF byte.

  • BenchmarkNativeFNV1Closure is the same but instead of calling an ordinary function each iteration, it invokes a locally defined closure.

  • BenchmarkGoopFNV1 defines a Goop object that contains a single data field (the current hash value) and no methods. Each iteration performs one Get and one Set on the Goop object.

  • BenchmarkMoreGoopFNV1 replaces the hash function with an object method. Hence, each iteration performs one Get, one Set, and one Call on the Goop object.

  • BenchmarkEvenMoreGoopFNV1 adds support for type-dependent dispatch to the hash-function method. Although only one type signature is defined, Goop has to confirm at run time that the provided arguments do in fact match that signature.

Another way to interpret the data shown above is that, on my computer at least, a function closure is essentially free; Get and Set cost approximately 33 ns apiece; a Call of a nullary function costs about 724 ns; and type-dependent dispatch costs an additional 1723 ns.

How does Goop compare to various scripting languages? Not well, at least for BenchmarkMoreGoopFNV1 and its equivalents in other languages. The following table shows the cost in nanoseconds of an individual BenchmarkMoreGoopFNV1 operation (a function call, a read of a data field, a 64-bit multiply, an 8-bit exclusive or, and a write to a data field):

Language Run time (ns/op)
[Incr Tcl] 8.6.0 24490
Go 1.3.1 + Goop 794
JavaScript 1.7 (Rhino 1.7R4-2) 682
Perl 5.18.2 622
Python 2.7.6 604
Python 3.4.0 567
Ruby 1.9.3.4 513
Python 2.7.3 + PyPy 206

In short, you'll want to do most of your coding in native Go and use Goop only when your application requires the extra flexibility that Goop provides. Then, you should cache as many object members as possible in Go variables to reduce the number of Get and Set calls.

Copyright © 2011, Triad National Security, LLC. All rights reserved.

This software was produced under U.S. Government contract 89233218CNA000001 for Los Alamos National Laboratory (LANL), which is operated by Triad National Security, LLC for the U.S. Department of Energy/National Nuclear Security Administration. All rights in the program are reserved by Triad National Security, LLC, and the U.S. Department of Energy/National Nuclear Security Administration. The Government is granted for itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide license in this material to reproduce, prepare derivative works, distribute copies to the public, perform publicly and display publicly, and to permit others to do so. NEITHER THE GOVERNMENT NOR TRIAD NATIONAL SECURITY, LLC MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES ANY LIABILITY FOR THE USE OF THIS SOFTWARE. If software is modified to produce derivative works, such modified software should be clearly marked, so as not to confuse it with the version available from LANL.

Goop is provided under a BSD 3-clause license. See the LICENSE file for the full text.

Triad National Security, LLC (Triad) owns the copyright to Goop, a component of the LANL Go Suite (identified internally as LA-CC-11-056).

Author

Scott Pakin, pakin@lanl.gov

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

Constants

This section is empty.

Variables

View Source
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

func (obj *Object) Call(methodName string, arguments ...interface{}) []interface{}

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

func (obj *Object) Contents(alsoMethods bool) map[string]interface{}

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) Get

func (obj *Object) Get(memberName string) (value interface{})

Get returns the value associated with the name of an object member.

func (*Object) IsEquiv

func (obj *Object) IsEquiv(otherObj Object) bool

IsEquiv returns whether another object is equivalent to the object in question.

func (*Object) Set

func (obj *Object) Set(memberName string, value interface{})

Set associates an arbitrary value with the name of an object member.

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.

func (*Object) Super

func (obj *Object) Super() []Object

Super returns the object's parent object(s) as a list.

func (*Object) Unset

func (obj *Object) Unset(memberName string)

Unset removes a member from an object. This function always succeeds, even if the member did not previously exist.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL