#Confusing method overriding behaviour

47 messages · Page 1 of 1 (latest)

mortal mural
#

Hi, i'm having some trouble understanding how method overriding works.
For context i'm making a ui library.

I have a WidgetInterface with some methods to be defined on all widgets

type WidgetInterface interface {
    GetWidth() int
    SetWidth(value int)

    GetMaxWidth() int
    SetMaxWidth(value int)

    GetMaxInnerWidth() int
    // ...
}

and a base struct Widget with some default implementations

type Widget struct {
    parent              WidgetInterface
    width               int
    maxWidth            int
    // ...
}
func (w *Widget) GetMaxWidth() int {
    if w.parent != nil && w.parent.GetMaxInnerWidth() < w.maxWidth {
        return w.parent.GetMaxInnerWidth()
    }
    return w.maxWidth
}
func (w *Widget) GetMaxInnerWidth() int {
    return w.GetMaxWidth()
}

I then have other structs for each type of widget. In the case of the ContainerWidget, i want the GetMaxInnerWidth to include padding.

type ContainerWidget struct {
    *Widget

    paddingLeft   int
    paddingRight  int

    // ...
}
func (w *ContainerWidget) GetMaxInnerWidth() int {
    return w.GetMaxWidth() - (w.paddingLeft + w.paddingRight)
}

However, when I go to call to calling w.GetMaxWidth() from another widget TextWidget, it does not seem to use the overriden version method, instead using the one defined in Widget.

Expected:
On a TextWidget tw
tw.GetMaxWidth() is called
tw.parent.GetMaxInnerWidth() where GetMaxInnerWidth comes from ContainerWidget

What actually seems to happen:
On a TextWidget tw
tw.GetMaxWidth() is called
tw.parent.GetMaxInnerWidth() where GetMaxInnerWidth comes from Widget

Method overriding seems to work for other methods so it confuses me a bit as to why it doesn't work here. Any ideas?

gray grotto
#

Can you give a working example? too many widget types and cant even see TextWidget

mortal mural
#

sorry yeah its a lot :)

#

What do you mean by a working example?

gray grotto
#

You're talking about something that isn't included in your example

mortal mural
#
type TextWidget struct {
    text       string
    faceSource *text.GoTextFaceSource
    color      color.Color
    wrapText   bool
    lines      []string
    *Widget
}

func (w *TextWidget) updateTextInfo() {
    face := w.getFace()

    lines := make([]string, 0)

    if !w.wrapText {
        w.width = textWidth(face, w.text)
        lines = append(lines, w.text)
        w.lines = lines
        return
    }
    var endIdx, p int
    maxWidth := 0

    for endIdx < len(w.text) {
        wi := 0
        endIdx = p
        startIdx := endIdx
        for endIdx < len(w.text) && w.text[endIdx] != '\n' {
            word := p
            for p < len(w.text) && w.text[p] != ' ' && w.text[p] != '\n' {
                p++
            }
            if wi > maxWidth {
                maxWidth = wi
            }
            wi += textWidth(face, w.text[word:p])
            if wi > w.GetMaxWidth() && endIdx != startIdx {
                break
            }
            if p < len(w.text) {
                wi += textWidth(face, string(w.text[p]))
            }
            endIdx = p
            p++
        }

        lines = append(lines, w.text[startIdx:endIdx])
        p = endIdx + 1
    }

    w.lines = lines
    w.width = maxWidth
    w.height = len(lines) * int(face.Size)
}
#

Is that what you mean?

gray grotto
#

And what widget is TextWidget initalized with?

mortal mural
#
    widget := &TextWidget{}
    widget.text = initialText
    widget.Widget = &Widget{
        maxHeight: math.MaxInt,
        maxWidth:  math.MaxInt,
    }
gray grotto
#

Okay and where does ContainerWidget come into play?

mortal mural
#

ContainerWidget is a parent of the text widget

#

sorry my example before was a bit overcomplicated

#
root := supui.CreateContainerWidget()
root.SetMaxSize(320, 200)
root.SetPaddingAllRound(8)

fmt.Println(root.GetMaxWidth()) // Expected: 320, Actual: 320
fmt.Println(root.GetMaxInnerWidth()) // Expected: 304, Actual: 304

container := supui.CreateContainerWidget()
root.AddChild(container)

fmt.Println(container.GetMaxWidth()) // Expected: 304, Actual: 320
half void
#

go just doesn't have method overriding when you change the implementation of GetMaxInnerWidth in ContainerWidget this does nothing

past pumice
#

TextWidget is embedding Widget, it's true that Wiget.GetMaxInnerWidth is called, not ContainerWidget.GetMaxInnerWidth.

If you want ContainerWidget.GetMaxInnerWidth to be called, embed ContainerWidget in TextWidget instead.

mortal mural
mortal mural
half void
#

or I don't understand what you mean

mortal mural
#

WhatAmI is defined in the interface and base Widget struct

half void
mortal mural
#

no I don't

half void
#

What would you want this code to print ?

type User struct{}

type Patient struct{
 User
}

func (Patient) TryingToOverride() string {
 return "was dispatched to Patient"
}

func (User) TryingToOverride() string {
 return "was dispatched to User"
}

func (u User) Print() {
 fmt.Println(u.TryingToOverride())
}

func main() {
 u := User{}
 u.Print()
 p := Patient{u}
 p.Print()
}
#

simple question there is no wrong answer

mortal mural
#

I'd want it to print "was disapatched to patient" but i'm guessing thats not the case :)

half void
#

indeed, you can run it on the playground if you feel like it

#

now what about this one ?

type User struct{}

type Patient struct{
 u User
}

func (Patient) TryingToOverride() string {
 return "was dispatched to Patient"
}

func (User) TryingToOverride() string {
 return "was dispatched to User"
}

func (p Patient) Print() {
 p.u.Print()
}

func (u User) Print() {
 fmt.Println(u.TryingToOverride())
}

func main() {
 u := User{}
 u.Print()
 p := Patient{u}
 p.Print()
}
mortal mural
#

"was dispatched to User"

half void
#

Sparkles ok so

#

In go the first one is rewritten to the second one by the compiler
||there are handful of edgecases different, none matter here||

#

If you can trust me and accept embeding is just weirdly named fields I think it make sense.
No where in the code Widget is told « btw you need to check GetMaxInnerWidth's implementation somewhere else ».
It does not even know it is being embeded to begin with

#

The example you gave where it "works" it's not working Widget never knows it's being embedded and never return the correct value.
When defining methods the last one defined up the chain wins and you call the outer object not the inner one so it gets dispatched to the outer one.

#

If you are familiar with inheritance linguo.
You are asking for virtual inheritance which allows childrens to override methods. This is often implemented with vtables, a vtable contain a list of various things among them function pointer, so each method call is looked up in the table and dynamically called, point is by making the method list dynamic you can update it at runtime while buiding the objects.

There is also non virtual inheritance in where you don't do any of the lookup thing and hardcode the function you are jumping to but that means any of your childrens can't update your methods.
Go only have non virtual inheritance ||altho no one calls it that in go, I've only ever seen non virtual inheritance in contrary to virtual inheritance for languages that support both, usually if you don't have virtual inheritance you say the language doesn't have inheritance||.

mortal mural
#

Are types seperate from this?

    fmt.Println(root.WhatAmI()) // ContainerWidget
    var rootagain supui.WidgetInterface = root
    fmt.Println(rootagain.WhatAmI()) // ContainerWidget

If i say root is the interface I still get ContainerWidget.

half void
#

what do you think should happen ?

#

I've been coding in go for so long I don't see what else this code could give

mortal mural
#

Would it not call the Widget WhatAmI method?

half void
#

Well it would not because the interface does not change Widget's internal.
Neither of the structs even know they are in an interface
In fact in go a lot of things only work backward.

What happen is this:

supui.WidgetInterface( // contains
  supui.ContainerWidget{ // Contains
    supui.Widget
  }
)

What you would need is this:

supui.ContainerWidget{ // Contains
  supui.Widget{ // Contains
    supui.WidgetInterface{
      &supui.ContainerWidget // pointer to the original object creating the cycle
    }
  }
}
#

note: the cycle thing technically works, and is actually how a language like C++ works under the hood but the cycle is being written by the compiler not you
I would not use this in any real go code, hard to maintain and debug and no compiler to help you

mortal mural
#

What would you suggest for my example? Should I try ditching Widget and have duplicated methods each time I create a new widget type?

half void
#

it's hard to say. All the code you show is so far removed from go's ideas I don't know what to do if not throw it away and restart from scratch.
It's not wrong or right, just it's not how go works and you will be fighting multiple uphill battles from different fronts.
You could also rewrite it in C++ or Java (right tool for the job kind of thing).

I havn't see the codebase chances most of the implementations can be kept but needs a heavy architecture make over.
I would look at how other UI frameworks design their API

#

If I understand this example correctly https://docs.fyne.io/container/box fyne is pretty close to what you are doing
except they completely inverted the architecture and use dependency injection.

mortal mural
#

Thanks so much for the help I really appreciate it! I will have a look through that, it definitely felt like I was fighting go with what I was doing.

half void
#

glhf, maybe you can change your thought process for our magnifique language, chances it'll take some time to get used to
we are definitely not a cult you can still do Java or whatever if you want ||why would you tho ?||

mortal mural
#

"we are definitely not a cult" - every cult ever