Skip to content

Instantly share code, notes, and snippets.

@atdiar
Last active June 28, 2023 09:14
Show Gist options
  • Save atdiar/6568964896231bfde734f6bddf9ff46c to your computer and use it in GitHub Desktop.
Save atdiar/6568964896231bfde734f6bddf9ff46c to your computer and use it in GitHub Desktop.
runtime: allow setting stable IDs for function types so that their methods can have access to per instance state

I have the following implementation for the Document Object Model API in Go (simplified example):

type Document struct{
    *ui.Element
    
    // list of constructors
    Body bodyConstructor
    Head headConstructor
    Div divConstructor
    // ...
}

type bodyConstructor func() BodyElement
func(c *bodyConstructor) WithID(id string) BodyElement{
    return BodyElement{newBodyElement(id)}
}

type divConstructor func() DivElement
func(c *divConstructor) WithID(id string) DivElement{
    return DivElement{newDivElement(id)}
}

// newDivElement intializes the base *ui.Element to behave like a Div. 
// We can then use a DivElement wrapper type to have access to DivElement specific methods.
func newDivElement(id string) *ui.Element{
    e:= newElement(id)
    e.typ = "div"
    // ... some other div relevant mutations
    return e
}

This allows us to create DOM elements by using these constructor functions as such:

doc:= NewDocument(documentID)

somediv:= doc.Div()
otherdiv:= doc.Div.WithID("otherdiv")

But here I have an issue.

The actual implementation of the constructors has to register the elementts on the document because we have a .GetElementById method on the Document type. So, on creation of a new document, we define the constructors as such:

func NewDocument(id string) Document{
    d:= Document()
    d.Element = NewELement(id)
    
    d.Div = divConstructor(func() DivElement{
        e:= DivElement{newDivElement(d.genRandomID())}
        d.RegisterElement(e)
        return e
    })
    // ...
    return d
}

Now, when an element uses the default constructor which doesn't have an id parameter, the element id is generated by the document and the element is also registered on the document. The issue is that when using the WithID method, iI cannot rewrite its implementation to have access to the document object and hence cannot register the elements that have a non randomly genmerated id, at creation.

This is annoying in terms of Developper Experience.

So basically, I need to be able to abstract over these constructor function types so that I can redefine them, while still conserving the fact that these are functions with a WithID method.

I can see two ways to deal with this that unfortunately are not (yet) available:

  1. Abstract away the Element constructors

I can instantiate a generic Element constructor function during document creation with one of the type parameters being a placeholder for the registration function.

Since the WithID method would have access to this registration function type argument, it would also solve the problem.

But it requires to be able to abstract over types as such so that I can assign these instantiated types.

type Document struct{
    *ui.Element
    
    Div interface{~ func() DivElement ; interface{WithID(string) DivElement()}} // not currently possible
}

type withIdiface[T any] interface{
    WithID(id string) T
}
type constructoriface[T any] interface{
    ~func() T
    withIdiface[T]
}

type genericConstructor[T any, PT *T, Register ~func(PT), C constructoriface[T]] func() T
func(c genericConstructor) WithID(id string) T{
    var r Register
    var cstr C
    t:= ctsr.WithID(id)
    r(t)
    return t
}

So here is another reason why union types implemented as generalized interfaces would be beneficial it seems?

[edit] this is still insufficient. The Registration type still needs to be parameterized by the specific document it is doing the registration for. This is basically requiring dependent types.

  1. Runtime stable IDs generation for reference values

Have a function in the runtime that allows the assignment of a stable ID to a value of the reference kind such as function. Then I will just be able to map constructor function stable IDs to the document they are defined on. Because, I think one can currenlty use SPrint nowadays to generate a similar value but it is not speced and is only valid as long as there is no moving compactor for instance. Basically, that's a way to have a stable reference instead of relying on pointers which would break if object are being moved around at some point.

It;'s a kind of hack that would enable me to access global state from a method, depending on the identity of the value it's being called on.

Does anyone see another way? (sometimes, my brain gets too myopic)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment