You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A fundamental issue came to my mind:
a sufficiently sophisticated interpreter can implement generics, in the sense that code passed to the interpreter can use generics, and the interpreter can implement the correct semantics for it. For example, gophernotes' underlying interpreter "gomacro" already has partial support for them, and implementing what's missing is not overly complicated.
But generic code in third-party libraries, which are currently compiled with Go toolchain and then loaded in gophernotes as shared libraries, pose a much more difficult challenge:
Go toolchain compiles generic code on-demand, i.e. only if it's instatiated and only for the types it is instatiated on. Go toolchain has no way to instantiate generic code at runtime, when an interpreter (or any other program) may need it.
The issue is easier to understand on a concrete example:
let's suppose that the hypothetical Go module example.com/go/stack contains the following:
when you type import "example.com/go/stack" in Gophernotes, it internally uses the Go toolchain to download and compile the module as a shared library.
If Stack was a non-generic class, the shared library would contain the reflect.Type for Stack, which allows creating Stack objects and calling their methods: that's exactly what an interpreter needs.
Instead our Stack[T] is a generic class: in order to do anything with it, it must be instantiated first. This means a reflect.Type for Stack[T] does not exist and cannot exist.
Only its instantiation on some concrete type, as Stack[int] or Stack[MyClass], can have a corresponding reflect.Type.
And here comes the issue: when import "example.com/go/stack" is entered in Gophernotes, there is no way to know in advance which concrete instantiations of Stack[T] will be needed.
Maybe Stack[int] ? Maybe hundreds of other instantiations, possibly using as T a type that does not exist yet, and will be defined later?
There's no way to know in advance the instantiations a user may ask for. So there's no way to compile them in advance.
This leaves us with two alternative solutions - and I am tempted to call them workarounds rather than solutions, because both have significant drawbacks:
every time the user tries to use a type Stack[X], where X is a concrete type, instantiate it by invoking Go toolchain, compiling a shared library, and loading it. It will be extremely slow and consume a lot of memory, but at least it works.
when import "example.com/go/stack" is entered in Gophernotes, do not compile the module using Go toolchain. Instead, load its source code in the interpreter, and use interpreter's support for generics to instantiate any Stack[X] that the user may attempt to use. This means that third-party libraries imported by Gophernotes will be interpreted - no longer compiled. They will be slow, and Gophernotes does not support .S files written in assembly, nor it supports Cgo. So a lot of libraries would no longer work when imported by Gophernotes.
Ideas for other solutions?
Any contribution is welcome :)
I don't remember whether gomacro is using reflect under the hood to manipulate types, but if it does, one could perhaps weigh in and propose to have a reflect API that can also manipulate parametrized types.
Yes, gomacro heavily uses reflect under the hood to manipulate types and values.
This has several limitations, including that reflect package cannot create named types or interface types - indeed gomacro must emulate them.
Generics are not supported by reflect, and if I remember correctly there's no plan to support them. Only instantiated generic types will be supported.
And I am quite sure there's no plan to support instantiating generics at runtime - which for the foreseeable future will require invoking the Go toolchain.
A third solution came to my mind: when importing a package, instantiate its generics on a custom type that satisfies the constraint(s).
First example: for the Stack[T] above, instantiate Stack[interface{}] when importing, compiling and loading example.com/go/stack with Go toolchain.
Second example: for an hypothetical generic type
typeSet[TOrdered] { /* ... */ }
where the constraint Ordered is
type Ordered[T] interface {
Compare(T other) int
}
we can instantiate, compile and load with Go toolchain the type Set[OrderedX] where OrderedX is
typeOrderedX {
X_interface{} // the wrapped valueCompare_func(otherOrderedX) int
}
// implement the interface Ordered[OrderedX]func (objOrderedX) Compare(otherOrderedX) int {
returnobj.Compare_(other)
}
This allows wrapping any type with a method Compare() inside an OrderedX and use it inside the instantiated Set[OrderedX]
Activity
[-]Implement generics[/-][+]How to implement generics?[/+]cosmos72 commentedon Oct 28, 2021
A fundamental issue came to my mind:
a sufficiently sophisticated interpreter can implement generics, in the sense that code passed to the interpreter can use generics, and the interpreter can implement the correct semantics for it. For example, gophernotes' underlying interpreter "gomacro" already has partial support for them, and implementing what's missing is not overly complicated.
But generic code in third-party libraries, which are currently compiled with Go toolchain and then loaded in gophernotes as shared libraries, pose a much more difficult challenge:
Go toolchain compiles generic code on-demand, i.e. only if it's instatiated and only for the types it is instatiated on. Go toolchain has no way to instantiate generic code at runtime, when an interpreter (or any other program) may need it.
The issue is easier to understand on a concrete example:
let's suppose that the hypothetical Go module
example.com/go/stackcontains the following:when you type
import "example.com/go/stack"in Gophernotes, it internally uses the Go toolchain to download and compile the module as a shared library.If
Stackwas a non-generic class, the shared library would contain thereflect.TypeforStack, which allows creating Stack objects and calling their methods: that's exactly what an interpreter needs.Instead our
Stack[T]is a generic class: in order to do anything with it, it must be instantiated first. This means areflect.TypeforStack[T]does not exist and cannot exist.Only its instantiation on some concrete type, as
Stack[int]orStack[MyClass], can have a correspondingreflect.Type.And here comes the issue: when
import "example.com/go/stack"is entered in Gophernotes, there is no way to know in advance which concrete instantiations ofStack[T]will be needed.Maybe
Stack[int]? Maybe hundreds of other instantiations, possibly using asTa type that does not exist yet, and will be defined later?There's no way to know in advance the instantiations a user may ask for. So there's no way to compile them in advance.
This leaves us with two alternative solutions - and I am tempted to call them workarounds rather than solutions, because both have significant drawbacks:
every time the user tries to use a type
Stack[X], whereXis a concrete type, instantiate it by invoking Go toolchain, compiling a shared library, and loading it. It will be extremely slow and consume a lot of memory, but at least it works.when
import "example.com/go/stack"is entered in Gophernotes, do not compile the module using Go toolchain. Instead, load its source code in the interpreter, and use interpreter's support for generics to instantiate anyStack[X]that the user may attempt to use. This means that third-party libraries imported by Gophernotes will be interpreted - no longer compiled. They will be slow, and Gophernotes does not support.Sfiles written in assembly, nor it supports Cgo. So a lot of libraries would no longer work when imported by Gophernotes.Ideas for other solutions?
Any contribution is welcome :)
cosmos72 commentedon Nov 2, 2021
@dwhitena @SpencerPark @sbinet any idea or suggestion on the best approach to move forward ?
sbinet commentedon Nov 2, 2021
I don't remember whether gomacro is using reflect under the hood to manipulate types, but if it does, one could perhaps weigh in and propose to have a reflect API that can also manipulate parametrized types.
cosmos72 commentedon Nov 6, 2021
Yes, gomacro heavily uses
reflectunder the hood to manipulate types and values.This has several limitations, including that
reflectpackage cannot create named types or interface types - indeed gomacro must emulate them.Generics are not supported by
reflect, and if I remember correctly there's no plan to support them. Only instantiated generic types will be supported.And I am quite sure there's no plan to support instantiating generics at runtime - which for the foreseeable future will require invoking the Go toolchain.
cosmos72 commentedon Nov 6, 2021
A third solution came to my mind: when importing a package, instantiate its generics on a custom type that satisfies the constraint(s).
First example: for the
Stack[T]above, instantiateStack[interface{}]when importing, compiling and loadingexample.com/go/stackwith Go toolchain.Second example: for an hypothetical generic type
where the constraint
Orderediswe can instantiate, compile and load with Go toolchain the type
Set[OrderedX]whereOrderedXisThis allows wrapping any type with a method
Compare()inside anOrderedXand use it inside the instantiatedSet[OrderedX]