Here’s a few responses to some of the overall “Smell” issues, and per 
Wojciech’s comments I don’t really do blogging but here are just a few of the 
“lessons learned / issues confronted” in this effort.  Overall the ONLY thing I 
would change about Go based on my experience so far is some of the messaging / 
docs that appear to go too far in attempting to distance itself from OOP and 
C++ paradigms.  In my experience, you can have the best of everything in Go, 
and sometimes a more standard OOP class hierarchy is exactly the right solution 
for a given problem (see below).

First, the essential requirements for a scene graph, and structural trees of 
that sort more generally, are:

* A common API for navigating, managing the tree

* But a diversity of functionality at each node (i.e., many different possible 
node types)

Thus, a common embedded base type that does all the basic tree stuff seems like 
the most natural solution.  Trees are not one-off things where a simple 
“Stringer” kind of paradigm is going to work.  They are containers, but unlike 
Go’s builtin, “privileged” containers with their magic generics functionality, 
they also naturally have a lot more complexity to them, and although my initial 
thoughts about this were that maybe Go2 could have a native tree container, I 
now think that doesn’t make sense.  The Ki tree was really easy to build on top 
of slices, and slices do all the actual containing work — the rest of it is 
just all the hierarchical infrastructure, which someone else might want to do 
differently, and definitely doesn’t belong as built-in to the language.

I also think the following functionality for a robust “tree” system is 
essential:

* An “end user” (in an app, or through a GUI) should be able to create nodes of 
any type at any point in the hierarchy, through some kind of general-purpose 
“InsertNewChild” kind of method, which takes as an arg the type of child to 
create.

* The tree should automatically support basic infrastructure such as JSON 
saving / loading, copying parts of the tree, etc.

This then introduces a lot of “generics” kinds of demands, but I found the 
combination of the interface and reflect mechanisms entirely adequate (if 
sometimes maddeningly frustrating in the case of reflect) for this job.  The 
addition of a type registry was extremely simple and I don’t see any 
“maintenance and reliability burden” engendered by this design (but maybe I’m 
missing something?).  Any new node type must be registered, so it is an extra 
line of code, but it is a simple, clear one, easy to copy/paste.  I also ended 
up adding a “New” function for each node because I was hitting massive 
slowdowns in reflect.New at one point (which turned out to just be the GC and 
nothing about reflect per say, and I minimized those by getting rid of all 
unnec. pointers) — could get rid of that and go back to reflect.New if people 
think that makes more sense?

One key trick for getting the most flexibility out of an Interface is to keep a 
“This” interface pointer of the struct in the struct itself (e.g., see 
ki.Node.This) — you can then ensure full virtual function calling in any method 
just by doing:

        n.This.Function()

It works great and always calls the properly overridden version of the 
interface Function() defined for the actual type of object in question.  I had 
a bit of an email discussion in this group with Ian about this back in March, 
and he emphasized how you really shouldn’t use any kind of C++ concept like 
inheritance in Go, but again I think that seems like throwing the baby out with 
the bathwater.  An interface variable absolutely has the equivalent of a C++ 
virtual function table, and it functions exactly as you’d expect such a thing 
to function.  If you redefine one of the methods in a derived type, it calls 
that method, even if you call it from an interface variable that doesn’t know 
anything at all about that derived type.  It is then just one simple step to 
ensure that you always have such an interface variable available.  And the 
automatic promotion of all methods from embedded types gives you exactly the 
behavior you’d expect: no need to redefine the methods that you want to just 
inherit — only override those that need to be specialized.

I also implemented another little trick for gaining access to the embedded 
struct type, like this:

        sl := recv.EmbeddedStruct(KiT_SliderBase).(*SliderBase)

so no matter what type of object “recv” actually is, as long as I know it 
embeds SliderBase, this code will work (KiT_SliderBase is just a simpler way of 
writing: reflect.TypeOf(&SliderBase{}), as a nice side-benefit of the type 
registry).  This doesn’t require defining an interface and is useful for ad-hoc 
cases where an interface is not really justified.

Anyway, to reiterate, my bottom line is that Go is awesome and supports 
everything you need, and that sometimes a classical OOP pattern of base-types 
and derived types that build on those is exactly what you need!  I do wonder if 
perhaps the allergy to that framework has inhibited development of a GUI in Go 
up to this point — e.g., I saw in both the Shiny and 
https://github.com/walesey/go-engine frameworks that there was some awkwardness 
about the generic container nature of a scene graph…  

- Randy

ps. several of the other code review comments reflect the “alpha” status of 
this project and can easily be cleaned up. The README files in particular were 
mostly just “notes to self” and really need to be rewritten. :)

> On May 4, 2018, at 3:35 PM, Randall O'Reilly <rcoreil...@gmail.com> wrote:
> 
>> 
>> The Ki interface is a code smell to me. Usually I interpret library 
>> interfaces as saying “the app provides an implementation of the interface 
>> and the library provides shared logic that uses the interface methods” or in 
>> this case I expect the ki lib will provide varying data structures and 
>> behaviors that implement the ki var. Just reading down to line 42 of ki.go I 
>> feel like this isn’t very Go-like.
>> 
>> You’re getting into generics territory with this:
>> 
>> func NewOfType(typ reflect.Type) Ki {
>> 
>> My view is idiomatic Go code tends to avoid this kind of thing due to 
>> maintenance and readability burden. I haven’t used your lib but I am 
>> concerned about it not being a big win over a per-app implementation. I can 
>> see you’ve done a lot of work to make generics work.

-- 
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.

Reply via email to