I have been struggling with getting the most optimal, non-reflective and performant way of implementing something that is a bit like OOP method overloading. As some here may know, I am implementing a novel binary tree based on complete trees and using ideas from B-heaps in order to achieve maximum data cache locality in a search and sort algorithm.
Basically I want to avoid rewriting any code. Note that this is not really at all an OOP paradigm that I am trying to ape with this. I want to isolate a small set of type specific functions from the much larger general functions that deal with tree walking, inserting and deleting. The consumer of the code needs to see something that looks like a type specific library, and not have to concern themselves with any type assertions or anything that complicates their code so that in every way the calling code will look the same except for the type of the payload being different and I am not excessively concerned with a small amount of call overhead, as it is my belief that the data locality structure performance will far exceed the small extra cost for this encapsulation. If I am wrong, I suppose I can rework it using a code generator to spit out type specific code, but I don't want to do this at this stage because it complicates my workflow in completing the proof of concept. So, I'll just briefly explain what I have worked out. Note that I am still really a beginner with Go coding, and my reason for posting this is to get a little more feedback in case there is any other matters I have not accounted for in my construction. I have the base type that concerns itself with the walk, search, insert and delete functions in an interface. Some of the functions deal solely with the tree node payload type, and some also require as well tree walk functions that are aware only of the abstract characteristics of the concrete type, but have a small contact surface with the data type for the reason of the page structure as well as the use of zero as the empty node. (This theoretically could be changed in an implementation where zero is a valid value but at the same time in a binary tree, implicitly zero is the left-edge node anyway and in a binary tree we are trying to keep a sort structure so there is only one place a valid null would ever be anyway) What I have done is create a type struct that gathers all of these functions that touch the concrete type, and a set of slightly differently named functions from the ones the caller will use, that also need to be passed the structure gathering the type specific functions and the walk and search/insert delete functions that implicitly must be type aware but only of the abstract features of the data, greater, less, equal and empty. The concrete typed library then contains the type specific functions, these are basically comparators, and then the end-user functions are written that call functions in the base library that requires access to these type specific functions, these are the walk functions (the walk to parent function does not need to interact with the concrete type, it only needs to understand the geometry of the data structure), but the left/right child walks need to be aware of the type as in the structure zero is my edge/empty sentinel. Then finally the maybe unique lateral walk functions which use the left/right walk functions, these functions need to use the simple left/right child walks but also need to know the boundaries of the data structure as the allocation of pages in the tree needs to be sparse in order to minimise memory utilisation at the bottom edges. It is a complete tree implementation, essentially, but done in the same general way as B-heaps, in order to maximise the performance gain by staying within the CPU cache. Of course, a simple code example is needed. First in the base library: type O struct { IsLeft, IsRight, IsEqual, IsEmpty func() bool IsOutsideTree, IsInsideTree func() bool LeftChild, RightChild func() } type B interface { // These only deal with the concrete type IsLeft(interface{}) bool IsRight(interface{}) bool IsEqual(interface{}) bool IsEmpty() bool // These deal with the concrete type as well as the parent type BIsOutsideTree(O) bool IsOutsideTree() bool BIsInsideTree(O) bool IsInsideTree() bool BLeftChild(O) LeftChild() BRightChild(O) RightChild() BLeft(O) Left() BRight(O) Right() BFind(O) BFind(O) Find(interface{}) error Binsert(O, interface{}) error Insert(interface{}) error BDelete(O, interface{}) error Delete() error } I have only included in the above the functions that actually have any contact with the concrete type. Then, just one example, of how I overload or whatever it might be called, so that the base library can depend on the composed/embedded derivative of the base interface: // LeftChild - func (b *BastU32) LeftChild() { b.BLeftChild(b.Overloads) } The function called in this can then use the functions passed into it without knowing the type, as you can see none of them accept parameters or return type specific values. I bundle all of the functions in the structure at the top because while several only require one or two of these functions, some require all of them and passing the individual functions makes for a lot of typing in the concrete type library. The b.Overloads - I am not sure, whether it should be pass by value or pass by reference. I think pass by reference adds an extra resolution step, but it is probably more expensive as every call of the functions will have to perform this dereference. I hope this is clear enough and detailed enough to explain in adequately concrete detail what I am trying to achieve and how I am doing it. I don't think this is OOP patterning, as I only want to have one level of abstraction and the only reason is that it reduces the size of the code that needs to be maintained. The base library does not care what the payload type is, only how to do these comparisons (the first 4 in the interface) and the rest either need those comparators or need a function that needs the comparators. Using first class functions seemed to be the simplest way to implement this. It was not otherwise possible to get the base library to use the type specific library functions other than by passing the functions and rewriting them to use the passed functions, and being there is a reasonably sized collection, with 8 functions, and especially the ones in the bottom half, nearly all of the set in the struct are required. I know the base library functions naming scheme is maybe a bit clumsy, but I don't think it's that important. I think it would be nice if there was some less wordy way to perform the override but this would literally be the most direct low level procedural programming way of doing it anyway, and as I understand it OOP languages hide this but that's how they implement. The big advantage is I can keep all the tree and abstract type aware code in the base, and implementation for a new type is not excessively long winded. All the non-B-prefixed functions have to be written more or less the same as the leftchild example above, and the New function that returns the 'class' to the caller only directly see the type relevant stuff, the abstract stuff is not directly exported. The concrete type library is all the user of this library needs to know in order to use it. They would have to go out of their way to tinker with the base library. Because of Go's package scoping rules everything has to be exported or this would not be possible to implement in separate packages, and stuffing the different type implementations would both be ugly in terms of naming the packages. Well, maybe I am wrong about that. If they all lived in the same package folder all the delicate internals could be not exported and thus not directly accessible. I think that it is important to keep them apart so that another user of the library could either make their own independent repository that imports my base library, as there likely is a whole mountain of possible variations. For one thing, key/value pairs, sorted by value, where the payload data is a struct containing a hash key and something that refers to another data set. I designed this originally for a purpose involving searching for half hash-collisions, and so for that there would need to be distinct comparators that only compare the first or last half of the data in the payload, though in every other way the code would be the same. Possibly these comparators could be overloaded by a user so only the greater/less function (isleft isright) are different. Thanks in advance for any feedback on this. I struggled for a long time to figure out how to do this and I couldn't quite grasp related things. I don't think there is really that many real cases where this kind of abstraction actually serves any purpose, but any kind of search/indexing system likely will need to be able to separate the payload from the organising structure. And in an application where several are used (again, my intended purpose for developing this) as well as being easier to maintain code, the tree side of things is only needed to be stored once as it is the same for any other payload data type library using this one, which would reduce pressure on the code cache as well. -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.