4 ms·
> Instead, you must define a bunch of structs and use a dummy interface that all nodes implement. This point interests me. I personally eschew 'dummy' interfac
by lowmagnet 10y ago
> Instead, you must define a bunch of structs and use a dummy interface that all nodes implement.
This point interests me. I personally eschew 'dummy' interfaces in my Go code, preferring to work with the commonalities of such an algebraic type.
For your Node example, I'd look for things that all of the nodes do or represent and work from there. The interface type built on that would have other bound functions using those commonalities to synthesize common functionality.
Remember that in Go interfaces allow one to add disjoint configurations, such as things that (to borrow from webdriver) accept Click() (like buttons) or Set() (input fields). Those interfaces can apply independently from the meat of being a webelement. By implementing (or mixing in) the behavior, you can have a richer whole.
- lobster_johnson 10y agoThe problem is that these data structures don't have behaviour. They're pure, immutable data. The fact that I have to wrap them in an interface is also a performance concern, because an interface devolves to a pointer if it can't fit the contents into a single machine word. So in effect, every single struct becomes a pointer, which in my case seems unnecessary. There are some concerns that cut across all uses of the data that could be methods, but even things like mapping over (transforming) the data shouldn't really need to be built into the data structures via interfaces. Not least because it's horrible to write. For example, consider this: type Expression interface{} type And struct { LHS, RHS Expression } You want to be able to do something like this: newExpr := Map(expr, func(e Expression) Expression { if and, ok := e.(And); ok { if and.LHS == True { return and.RHS } if and.RHS == True { return and.LHS } } return e }) How do you implement Map? You have several choices. You can let Expression have Map(), and then every expression needs to implement it: type Expression interface { Map(fn func(e Expression) Expression) Expression } func (e And) Map(fn func(e Expression) Expression) Expression { e.LHS = fn(e.LHS) e.RHS = fn(e.RHS) return fn(e) } Or you could do something like: type Expression interface { GetChildren() []Expression SetChildren(...Expression) Expression } func (e And) GetChildren() { return []interface{e.LHS, e.RHS} } func (e And) SetChildren(children ...Expression) Expression { e.LHS = children[0] e.RHS = children[1] return e } Now you can implement Map() once for all expressions: func Map(e Expression, fn func(e Expression) Expression) Expression { children := e.GetChildren() newChildren := make([]interface{}, len(children)) for i, child := range children { newChildren[i] = fn(child) } return e.SetChildren(newChildren...) } ...but at the cost of being expensive, due to slices. Less expensive idea: Since all my expressions are nonary, unary or binary, I could have a Nonary/Unary/Binary to avoid GetChildren() and SetChildren() operating on slices, and then Map() could simply do a switch. Or you could simply skip all of that and build a switch statement: func Map(expr Expression, fn func(e Expression) Expression) Expression { switch e := expr.(type) { case And: e.LHS = fn(e.LHS) e.RHS = fn(e.RHS) // ... } return fn(expr) } This is why I was considering generating code, because it's terrible having to write and quality-check all that boilerplate. (There's a lot of boilerplate: Map() is not the only function I need. I also need a top-to-bottom version (the above is depth-first) and a Find(), and multiple other things that need to recursively determine something about an expression, without knowing all the types involved.)