Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Cleaning up Golang Polymorphism/Inheritance/Composition

Unjumbling Golang OOP primitives

I am in the process of writing a binary search tree implementation and in order to minimise maintenance problems, I needed to use parametric polymorphism - so I could specify part of the algorithm concretely, where it is in common regardless of the data type in a subclass, and which parts were not in common.

The issue that I came across and I only just finally understood how to do this type of code in Go yesterday, so this is fresh and an issue that I want to raise regarding the Golang 2.0 specification.

Currently, this is how you do OOP in go:

type Objectname struct {
  privateVariable typename
  PublicVariable typename
}

type objectname interface {
  privateFunction(parameterTuple) typenameTuple
  PublicFunction(parameterTuple) typenameTuple
  NonImplementedFunction(parameterTuple) typenameTuple
}

func (o *Objectname) privateFunction(parameterTuple) typenameTuple {
  // ...Implementation
  return typenameTuple
}

func (o *Objectname) PublicFunction(parameterTuple) typenameTuple {
  // ...Implementation
  return typenameTuple
}

func (o *Objectname) NonImplementedFunction(parameterTuple) typenameTuple {
  // put nothing in here
  return typenameTuple
}

Now, it's not excessively complicated, but it does lead to readability issues by not prescribing a metastructure for this. What I have composed here is essentially what you have to do, but you can see that the only things that tie everything together are repeating strings, the objectname, function signatures, the type binding declarations in the functions.

It is a purely 'syntactic sugar' issue, really, but an important one. If there was instead a sectioned structure for this, you could avoid scattering the connected elements of the type amongst non-typebound accessory functions and variables. A simple example of how this can happen is with a binary tree library - you need to have a cursor type for tracking walks and moving data into other functions and variables, but it's a second type and really should be kept together with the class that requires it. This is just a simple case, it would get worse the more necessary accessories tie together but are not big enough to be separated into distinct files (what purpose would a cursor library have without the implied context of the tree mapping system?).

I would like to see it become possible to rewrite the above code like this:

class Objectname {
  struct {
    privateVariable typename
    PublicVariable typename
  }
  
  methods {
    privateFunction(parameterTuple) typenameTuple
    PublicFunction(parameterTuple) typenameTuple
  }
  
  interfaces {
    NonImplementedFunction(parameterTuple) typenameTuple
  }
  
  // below specifies the binding reference name for functions
  bind o {
    func privateFunction(parameterTuple) typenameTuple {
      // ...Implementation
      return typenameTuple
    }
    func (o *Objectname) PublicFunction(parameterTuple) typenameTuple {
      // ...Implementation
      return typenameTuple
    }
  }
}

It is superficial, and could be implemented using a preprocessor, in fact (so it fills all the necessary non-implemented functions with dummies), but it would encourage more careful structuring of source files, and make it easier also to see when it makes more sense to split a source into two or more separate files.

@l0k1verloren

This comment has been minimized.

Copy link
Owner Author

commented Apr 20, 2018

Also note that go fmt could potentially help you by matching the bind section's ordering with the methods section.

@l0k1verloren

This comment has been minimized.

Copy link
Owner Author

commented Apr 20, 2018

It also occurs to me that go fmt could help a lot without changing the language spec.

  • Interfaces order of listing could be used to match the position of functions listed directly after the interface
  • It could automatically generate empty dummy functions with dummy godoc comment headers, and automatically place them at the end of the interface list while they are empty (as in, no content inside the function braces, except for the mandatory return with empty default values)
  • Where an interface type signature collides with a type binding, the type struct and type alias entries connected via the interface function signatures could be placed directly above the interface declaration

With these formatting conventions baked into go fmt code would become instantly more readable and maintainable, and many common interface/OOP related error messages could be eliminated, and automatically grouping data type and interface declarations with implementations would make it very obvious when a source file could or should be split into two, to separate domains cleanly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.