-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
proposal: Go 2: blank types as an alternative to generics #21132
Comments
This presentation is missing to describe WHY this would be an interesting addition to Go. What class of problems can be solve with it ? And what class of problems can't be solved with it. |
I don't think it is obvious. I know what problems other generics proposals would solve, the first thing which comes to mind are data-structures. There aren't really any use cases which come to mind for this one. |
Is this legal?
or, the more generally seen:
IME, the two common use cases for generics are data structures like trees, as you point out, and the common array/list manipulation functions like What implementation detail leads you to include this constraint? |
I think this use case
is EXACTLY what most people use generics for in other languages. So if it does not support it, I personally thing it's a no-go. |
With this proposal, I still wouldn't be able to avoid codegen for libraries like linked list |
It's as if OP wants generics but he doesn't want generics. Some major cognitive dissonance here. Either do it properly or not at all. |
Exactly. Worst case as an alternative consider reified generics only (like Java), but this seems like a proposal that really does not address the most common use case for generics. |
Is one of the use cases of generics to have the exact same functionality as golang's "interfaces" but do the cpu cycles at compile time, instead of run time checks, for type checks and code that changes based on the type being passed in? I have a hunch some people are skeptical of go's interfaces because they are a run time check when go is a static compile time language. But, you don't want a template/generic system to end up like a C++ mess. One issue, is anything that uses <> or $ looks like c++ or perl, respectively: but that's not a technical issue, it's an aesthetic one. For an interesting compile time, non object oriented generic system using a compiler preprocessor, I wrote this little riddle many years ago: The same could be done with plain C since it has an include file system. The idea, was that it was a prototype, not meant for production, but to spread ideas on how to implement a non object oriented procedural/functional strongly typed generic/parametric polymorphism system that does not determine types at run time, but compile time. It might be important to distinguish:
I'd prefer a generic system that doesn't require one use an object or struct - i.e. some languages tie the generic system into classes or objects, which, IMO is absurd and ridiculous, since non OOP data passed into regular procedures should also be generic... not just classes/objects. But, maybe that's what parametric polymorphism is. This should be clarified, IMO, in computing science (maybe even by myself, in an official article if I am somehow able to find the answer). |
And another use case, when looking for solutions generics solve, might be:
Just some things I have come across anyway. Is it a warning sign when you find code using an empty interface or lots of interfaces.. is a question one might ask |
yes this is legal or, the more generally seen:
yes this is legal
why do you think that map() fold() and contains() is not possible? Can you elaborate?
Which constraint exactly? |
I feel that this half measure has comparable costs to generics without the benefits. A good generics implementation can make code smaller (e.g. generic data structures), simpler (e.g. result types, map/filter/reduce), clearer (e.g. interface/trait bounds on types), and safer (static guarantees on the types in all these contexts). This tackles map and filter (and maybe reduce?) but since it's only lexical it can't provide generic data structures and can't restrict the types which fill in the blank (which invites some surprising error messages, unclear code, or code that compiles and seems correct but isn't). The presence of the feature would also make a real generics implementation more difficult. |
Finally someone who read the proposal carefully. Bravo!
I appreciate your honesty to express that this is your feeling and not a proven point. That is a trade off I made based on the following ideas:
The second point why I think this trade off is acceptable is my feeling, that having type independent functions (generics or blank functions) makes only sense if we have n uses of m type independent functions, where n is much larger than m. In other words: much code reuse. And with much code reuse the investment into the code that gets a lot of reuse will pay off even if it is more complex than the usual code.
You've pointed out the main use case of this proposal (and from there we have all the examples), that are map/filter/reduce kind of functions.
The lack of restrictions is a valid point. However that could be provided by union types which is a feature orthogonal to blank types. Can you give me an example, in which case you would expect "surprising error messages, unclear code or code that compiles and seems correct but isn't"? Thanks for taking the time, trying to understand the proposal. |
@ all Updated the proposal to add ideas of how a full feature parity with generics could be reached. |
What would the following program (legal according to your specification AFAICS) print?
|
It would print
Since at runtime a blank type is just an empty interface. (And the returning values are unboxed). |
If we would forbid the assignment shortcut in Go2 (":=") which would be a different proposal, then it would probably be easier for the compiler to check the type soundness and infer the resulting type in your example. |
OK, that's something your proposal doesn't specify (it would need to). Next: what would this print?
|
Checking that "blank types" work in context is equivalent to typechecking generics. While having to generate and then link more functions would occur if you used template-style generics, this is effectively the same idea as just making generics equivalent to Object in Java.
Generics provide this, in addition to possible performance gains. Why throw those performance gains out the window if you're doing all the work to get there anyways?
How is there a real and fake type system at play here? There is only one type system, and that's what's exposed to the user. If you write
I think this should be "a pure semantic feature," since the compiler is proving that the semantics make sense -- lexical soundness would just mean that the syntax can be parsed pretty much. If it were purely lexical, it would effectively be a comment placed on an
Unless you don't consider typechecking to be part of the overhead of generics, this is 100% adding that overhead into Go. To me it seems that the only difference between what you're doing and classic generics is that you're artificially limiting the feature to functions. |
Checking that a blank function is sound is not so simple. According to the operations performed on the blank typed values in the function, the allowed types would be restricted. For instance, calling a method or applying an arithmetic, logical or binary operation on it. It would not be a simple lexical sugar. Another problem with this proposal is that the user would have to look at the function implementation to see what type can be used with the blank function. This makes developpment more painfull. There is much more to keep in (brain) memory to be able to use them. That is not good because it means more stress on the brain. I'm still looking forward to see a real use case where the absence of generics in Go is a real problem. |
Thanks for your comment. It helped improving the proposal which resulted in the last updates. Please read the new section "Walk through the compilation steps" that provides a deeper insight. I have made some small changes (UPDATE10 and 11) which were errors I discovered when trying to answer your questions. The BNF would have rules for a blank type and a blank function where a blank function is any function that has a blank type in its type signature. Blank types only appear in type signatures (see UPDATE10) and carry no specific semantic meaning (no value) other than their name (is pratically like nil: always its own identity). That's why a called it a lexical feature. Feel free to suggest a better wording. However when it comes to the scoping rules, there is only one rule to check: But I think it will become more clear, if you read the walkthrough. Regarding runtime optimization / performance it should perform the same way as now if you are using the empty interface{} where you want the generalization (see container/list example). So compared to Go1 it would add type safety. |
I've added a compilation walkthrough to help understanding where there complexity for the compiler lies. Please check. (Also made some updates on the way, 10 and 11). Concerning what you said about type restrictions, I think you misread it somehow:
I probably failed to make clear that any other expression containing a "variable" of the "blank type" (which it is only while parsing, not while compiling) is not allowed and will result in a compilation error. I will update the proposal soon to make it more clear. So arithmetic, logical or binary operations are not allowed on a parameter passed in as a blank type. As simple as that. I think you are heading to common functions for numbers of any kind. To enable that, I would suggest to add union types to Go2 (which is orthogonal to this proposal) and then use the restrictions of the union types in combination with a type switch to get that behavior. I think now it should have become clear that the use has not to keep anything in his head to use the blank functions other than the signature and that there is no restriction what type can be used with it. It is just a restriction which expressions may appear inside the body of a blank function. |
it would output the same as
see the "Open questions" section which addresses similar aspects. |
Maybe a generic runtime internal "unbox" function could help with something like This unbox function would then be injected by the parser when preparing the parsedBlankFuncs. |
The real type system is the one that can be inspected at runtime. There is no blank type at runtime; it is just interface{}. |
* is not a kleene starit is the same thing like *$A in your proposal. a generic pointer to arbitrary type no i just have an old document from time when it was just an idea: |
If it is a generic pointer doesn't that mean that it is always on the heap? |
Where's that specified?
Are you saying that:
is not allowed but:
is? |
I implied it at some point, without being explicit (thanks for the hint). Did add the following while answering your question: "The blank type itself may only appear inside the function signature of the blank function."
is not allowed, but the following is (principle is: "bring in your types"):
|
@metakeule For example, your proposal wouldn't even allow the writing of a replacement for the append language built-in - something that's only built in because it's not possible to write a generic function to do it. I think you should take this one back to the drawing board. |
You can build type safe wrappers around generic types and functions dealing with empty interfaces. This way you can implement anything, although not at the same speed as the native type. I don't think this is pointless. So an append build this way would be based on []interface and wrapped and would probably not be as fast as the builtin append. But this proposal does not aim to replace existing builtin generic functions but to complement them. |
@metakeule We can do that currently. How does this proposal help? |
Type erasure in fact? Java programmers already have trouble with it. |
It helps in that you don't need to build a type safe wrapper for each type, but just once for all types. |
It doesn't look like that from your comments above. For anything that involves actually naming the "blank" type, you'd need to build a type safe wrapper for that specific type. |
Can you give an example? I am not sure, if I understand you properly... |
@metakeule As a very simple example, try to write an implementation of "append" that works for all slice element types and doesn't use the language built-in append function. |
here we go package main
import (
"fmt"
)
type Slice interface {
Append($A)
}
type slice struct {
data []interface{}
appendFn func ($A)
}
// now here is where the meat is
// the anonymous callback function appendFn
// is defined in a way that makes sure type $A stays the same as when NewSlice was called
func NewSlice(a $A) Slice {
s := &slice{
data: []interface{},
}
// not that it would make sense to append this way, just for demonstration
s.appendFn = func (a $A) {
_old := s.data
_new := make([]interface{}, len(_old)+1)
for i, o := range _old {
_new[i] = o
}
_new[len(_old)] = a.(interface{})
s.data = _new
}
}
func (s *slice) Append(a $A) {
s.appendFn(a)
}
func main() {
s := NewSlice("") // slice of strings
// s.appendFN is now func (string) for (pre)compilation time,
// if not set (nil) -> compilation error
s.Append("hello world") // should compile
s.Append(42) // should not compile
} |
@metakeule Thanks for the example, although I don't fully understand it. In particular I don't understand how you can use Or, if the underlying data type is always Maybe that is all this proposal (which I don't fully understand) is intended to provide, but I think a good generics implementation needs more than that. |
+1 to @ianlancetaylor's remarks. But, additionally, the example seems to directly contravene one of the rules stated in the proposal:
There are at least two type definitions in the example that contain a blank type. |
We are almost there ;-) First of all: This is an "alternative" to generics, not an "implementation" of generics. To value the proposal, please compare it to the current state. We now have several places where interface{} is used within the standard library and where performance apparently is no issue, but type safety is. The example provides a solution to the type safety issue. For performance critical application you can always use code generation like before. That being said, in the given example, there is room for an optimized solution with func Append(a $A, x []$A) ([]$A) {
return append(x,a)
} But @rogpeppe asked for a solution without using the built-in Hope now it is clearer: 2 ways of generic features:
The exception to the rule is: they are allowed inside function signatures. Please suggest a better wording. What I meant to say is: type Slice interface {
Append($A)
} An interface is seen as a collection of function signatures. type slice struct {
data []interface{}
appendFn func ($A)
} A callback function that is a property of a struct would count as a function signature. In both cases the function signature is just informal (represented by some []byte in the exported types) and will only be used in the precompilation step when the blank types are checked (it may also be shown be some reflection helpers, that is to be decided). From the runtime perspective type Slice interface {
Append($A)
} would behave as type Slice interface {
Append(interface{})
} and type slice struct {
data []interface{}
appendFn func ($A)
} as type slice struct {
data []interface{}
appendFn func (interface{})
} |
ContextI've developed a tool for writing generic/reusable code. In short it allows a code be used as a template, for a specialized/concrete type-safe code. It preserves the specialized parts, the modifications made to the origin. For example it is possible to redefine After sometime using it, from the observations, I came to this conclusion that something that is not parameterized, can not be generalized. It may sound obvious, but rephrasing it this way, brings interesting facts into light. Sample Case, Generic Sortable SliceThe code that is used as the generic/template code is: package sortable
type Sortable []interface{}
func (s Sortable) Len() int { return len(s) }
func (s Sortable) Less(i, j int) bool { panic("N/A") }
func (s Sortable) Swap(i, j int) { s[i], s[j] = s[j], s[i] } The So it is needed to override the // Code generated by github.com/dc0d/goreuse. DO NOT EDIT.
//go:generate goreuse -o strslice.go -prefix str -rn strSlice+=Sortable -rn Less+=Less sample/pkg/sortable
package main
import "strings"
type strSlice []string
func (s strSlice) Len() int { return len(s) }
func (s strSlice) Less(i, j int) bool { return strings.Compare(s[i], s[j]) == -1 }
func (s strSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } In this code the parameter type of slice is changed from ConclusionIf Back to something that is not parameterized, can not be generalized; as it can be seen, adding generic type parameter at type level, breaks Go at many different points. Maybe the thing that needs to be parameterized is not the type. We can have parameterized blocks or parameterized packages (or parameterized header files maybe), and then it is possible to generalized those parts. |
This is really embarrassing... generics should make things obvious and clear, rather than complex. Proper code generation could replace most of the issues described and provide much more powerful tool than generics themselves without any huge drawbacks. I'm currently using this when I need generics. // for <T>,<V> => int,int64 | int, int | float, int | float, float
func Sum<T>With<V> (t <T>, v <V>) <T> {
return t + v
}
// end and you can choose whatever syntax you love // for $T, $V => int,int64 | int, int | float, int | float, float
func Sum$TWith$V (t $T, v $V) $T {
return t + v
}
// end And if you have errors like assigning int = nil - go build will show you that during compile time, not at runtime. |
Two remarks:
|
There is no sane way to maintain generated code and have it shared across projects IMHO. |
@metakeule |
You mean, you never have a bug to fix inside the code template? Good luck in finding all the places where you used it. With a package dependency, you either can rename a package and do a build or do a tentative update. |
@metakeule What I'm proposing is to make this code generation explicit and integrated into build process to allow more than just generics. If you're talking about code snippet I've shown above in original message - it's true that it sucks, because that's something I'm currently using to overcome generics problem - not something I'd like to see in Go. What I'd like to see in Go is package mysort
func Sort<T>By<V> ([]<T>) {
// use T and V as plain code text that will be replaced during build
// for example <T>.<V> or "<T><V>" or <T>[<V>]
} where compiler determines types used based on function name template import "mysort"
type People struct {
Name string
Age int
}
mysort.SortPeopleByAge(peopleArray) Such approach allows you to write powerful, still explicit generics. |
@gladkikhartem that reminds me of a preprocessor/makro system and AFAIK the Go developers already did decide against this to keep the usage of the compiler simple and the speed of the compiler fast and predictable (can of worms). |
Whether this is an implementation of generics or an alternative to generics, this is clearly closed related to the general topic of generics, which is discussed over in #15292. Closing this issue in favor of that one. |
Reasoning
It has been said before, that adding generics would make Go (both the language and the Compiler) more complex and slower compiling.
The compilation of a function that deals with a generic type would need to be redone for each concrete type the function is used with (including non builtin types) to allow proper allocation etc. That would slow down the compilation of a large code base with many reuses of generic functions a lot. A main selling point for Go is the fast compilation speed which is partly due to incremental compilation. In the case of generics, incremental compilation is not possible, since the concrete type is only known where the generic function is used, not where it is defined.
This proposal goes another way to achieve the same that generics provide: Type safety for functions that deal with types that are unknown in the definition of the function.
But it approaches it a different way: Instead of adding new kinds of "real" types or "template" types, the here called "blank types" are just hints to the compiler to allow it to match them by comparison of their names (within defined scopes) at compile time.
At runtime however they are simply passed as interface{} values wrapping the real values since the type soundness already has been proven. In order to achieve that, some constraints have to be made, so that blank types don't "leak" into the (real) type system.
Blank types would help for some of the use cases that generics are used for, without adding the above mentioned complexity and overhead of generics.
With the given restrictions only the cases are allowed where the blank type does not
affect the generated code and thus eliminate the need to generate multiple versions of a function.
(With the experimental additions 1 and 2 feature parity with generics could be reached.)
Non goals
It has been expected that this proposal would allow to create functions that deal with some subsets of types, like e.g.
Add(a,b, Number) Number
. However the author thinks that this requirements is better served by union types (in combination with type switches) which also would make a nice addition to Go2, but which is an orthogonal feature that would complement this proposal in a nice way.Definition
Lacking a better name, we define a blank type as follows:
it is a placeholder for a type, represented by a dollar sign followed by a single uppercase letter (e.g.
$A
,$B
etc) (if a better representation could be found, this could change)the name of a blank type is its upcase letter after the "$"
it is just checked at compilation time. At runtime its value is passed around like empty interface{} wrapped values since the compiler did already prove type soundness.
it may not be used inside a type definition (e.g.
type GenericSlice []$A
is not allowed, alsotype $C string
is not allowed).A blank type and a "variable" of a blank type, may not be used outside a function.
A function using blank types is called a "blank function".
The blank type itself may only appear inside the function signature of the blank function. It is not possible to declare a new variable based on a blank type (or a new composed blank type) inside the function body. However "variables" of a blank type may be used as parameters and return values of builtin generic functions (see section "Compilation").
interface types might not be passed as arguments ending up as blank type parameters.
Example
here the blank type
$A
ofAdd1
andAdd2
are two different blank types, each only valid within the corresponding function.A blank type is a pure lexical feature, i.e. the compiler proves that it is lexically sound. There is no runtime check nor any need to keep track of a blank type within the AST (where there are the same concrete types as before).
A blank function might return a normal type or a blank type or any combination of them.
A returned blank type might be used as an input to another blank function, but only if the corresponding argument of the receiving function is also a blank type.
At runtime, a returned type will be unboxed (from interface{}) to its concrete type if the receiving variable is no blank type (the soundness had already been proven by the compiler).
A blank type that is going to be returned is only allowed, if it came to the function as an input argument , example:
this blank function signature is illegal, since $B does not appear within the receiving arguments.
this blank function signature is legal
Composition
Blank types have restrictions on composition.
The following composed blank types are possbile arguments to a blank function:
These are not valid / not allowed:
So there is no recursion inside the definition of a blank type and a composed blank type, with the exception of blank functions that may be used as callbacks to blank functions.
Compilation
The lexer/compiler checks at three points:
For 2. and 3. the compiler just has to check, if the concrete types matches.
Example:
The compiler finds that
$A
must be a string (since main passes a[]string
to the[]$A
argument ofGet
). Then the returning type ofGet
isstring
and so must be the type of the received argumenty
inside main.It has to be discussed, if shortcut assignment for the returned arguments should be allowed, since it puts some work on the compiler, so if this would be valid as well:
For 1. the compiler has to check the soundness of the blank function.
A blank function is sound if:
The body of a blank function is sound if the function compiles when the only functions, blank types are passed to are either builtin generic functions (definition see below) or other blank types. Also only blank types may be used inside the function body that are defined in the signature of the received arguments.
The following pseudo code contains all valid expressions where a "variable of a blank type" (that is only true for the parser) may appear. All other expression may not "use a blank type". (see the compiler walkthrough to get more insight).
When invoking the generic builtin functions, the blank type is treated by the compiler as if it was a actual type for the scope of the blank function. That means that only an "instance" of a blank type may be passed to a generic setter to set a composed blank type (or to append). Since there no other way to create an instance of a blank type, the only way to get them is from the received arguments, be it directly or through the use of a builtin generic getter.
Composed blank types may never be passed to generic setters.
The other way, blank types and composed blank types may be used, is to be passed to other blank functions. In this case, a blank type must correspond to a blank type in the receiving function and a composed blank type must correspond to a composed blank type of the same kind in the receiving function. Example:
Since there could be chains of blank functions it might be helpful for the compiler to generate a helper function for each blank function that returns the resulting concrete types from a concrete input arguments ("type calculation"). That would also help Experimental Addition1 and 2.
The other implications for the compiler, if the experimental additions were considered (which would result in full feature parity with generics) are pointed out in the sections Experimental Addition1 and 2.
Experimental Addition1
The following might be a way to expand. It is not sure if it will work out
(would need further investigation).
Interfaces may contain blank function methods or mixed them with concrete methods. Example
Also types may be defined on blank functions:
Then could probably modify the definition of a blank function in the case of an anonymous function where the scope of the name of the blank types in the signature of the anonymous function will be the same as the outer scope, in which the anoymous function is defined, e.g.
Here the
$A
in the signature ofLength
and in the signature of the anonymous function that is returned byLength
is the same, i.e. must be of the same type. However it might be hard for the compiler to prove.If we'd put these extensions together, we could probably build wrappers around composed blank types like this:
Experimental Addition2
Again a thought experiment (like experimental addition1).
Lets take the changes of experimental Addition1 and take them a step further:
Then we could wrap "generic" structs that deal with interface{} in a type safe way.
Example
For this to work without the compiler having to generate code for each type a blank function is used with, we need two things:
Compiler must generate helper code that infers the concrete types of the callback functions from the outer function if this blank type is reused. In the example that would mean for the compiler to do:
This small code which is independent from the concrete type must be exported (invisible just for the compiler), when compiling a library so that incremental compilation still works and when compiling a dependent package, this code will be used to figure out the concrete type for an instance of a blank callback function.
Then we need the second thing: Have a new property for callback functions that are blank functions: The concrete types that have been set via the code that derived it from the outer scope. So while compiling, this code must be run and attached to the struct field that is the callback function so that the compiler can prove type matching where this callback functions are invoked.
That would probably be the hardest part of the implementation but it would allow full feature parity with generics without code generation for each applied type.
At runtime however all values that pretend to be blank types are de facto like empty interface{} wrapped values (since the compiler did already prove the type safety).
Walk through the compilation steps
The following is by no means the optimal/optimized way to do it, but it provides deeper insight how this proposal would affect compilation.
After lexing and identifying blank functions, the parser checks the scoping rules for all blank functions and calls a special (pre-)compiler that creates a helper function for each blank function. Let's call this helper functions 'type calculators'.
The type calculators just calculates a concrete type of a blank function from input types.
Example (the examples have no practical sense, they are just short to illustrate without distraction)
Now let's define "parsedBlankFunc" as an intermediate type for the parser to help type checking when proceeding the invocation of a blank func. Such a parsedBlankFunc would consist of a regular func that is the same code as the blank function but with any blank types replaced by
interface{}
. Before this replacement takes place, the soundness of the body of the blank function is checked. This has to be done expression by expression while keeping track of the variable that are supposed to have a blank type. There are restriction in which expressions such variables may appear.(see above). Only the following expressions are allowed:
Any other expression where a "blank variable" appears is invalid and leads to a parser error. If the body is valid, all appearances of blank types within the blank function (in the signature and the body) will be replaced with "interface{}" and the resulting function will be reparsed and this reparsed version will be attached to the parsedBlankFunc. (this reparsing step could take place at a later time, e.g. when the rest of the code is parsed).
In addition parsedBlankFunc has a map of type calculators for its blank types out of the given parameters and obviously it has a structure telling it where the blank types have been inside the type signature of the original blank func.
For the scoped blank callback we define a "parsedBlankCallback" as consisting of a parsedBlankFunc and a reference to all outer scoped parsedBlankFuncs and parsedBlankCallback that have blank types of the same name.
Now, after all type calculators, parsedBlankFunc and parsedBlankCallback have been created, the parser now parses the rest of the code tree.
While doing so, whenever a blank func would have been called, the resulting types are calculated via the type calculators for that parsedBlankFunc. When ever a blank callback would have been created, the corresponding parsedBlankCallback is provided with the arguments of each needed surrounding scoped parsedBlankFunc (or parsedBlankCallback) and asked to calculate the types based on the surrounding parameters and based on its own parameter and validate them against each other where the names are matching. Also the returned types are checked against the receiving variable type and code is injected to do the correct unboxing (the types have been validated).
Now the type checks are done, the AST has been build, and the compiler can proceed without having to know about blank types and blank functions at all.
To enable incremental compilation, the parsedBlankFunc and parsedBlankCallback and their type calculators would need to be exported to the build of a non-main package, so that the parser would not have to recreate them.
Compatibility with Go1
Currently dollar signs are not allowed as identifiers.
The change would only allow the new syntax, the old syntax would still work.
Use cases...
Map(), Filter(), Reduce() etc.
With Experimental Addition1 and Experimental Addition2 feature parity with generics could be reached by implementing generic untyped code based on interface{} and then wrap it as shown in Experimental Addition2. This however would make the compilation more complex.
Open questions
It is unclear if the builtin "generic" functions like append, copy etc. could be make to work with blank types, i.e. an append([]$A, $A) must behave like an append([]interface{}, interface{}) although the underlying slice would be []int, []string, whatever at runtime. The same goes for maps. Pretty much every builtin generic function will get some sort of (probably annotated) interface{} value at runtime, while having to deal with the underlying concrete type.
A solution could be to create a generic runtime internal unbox function that unboxes the passed value (coming in as an interface{}) to it's underlying type. This function could then be injected to the code when the parser prepares the parsedBlankFuncs.
Benefits
Disadvantage
UPDATE: added generic type assertion as a way to deal with blank types and section about further exploration.
UPDATE2: added reasoning section and pointers to use cases
UPDATE3: change section "Further exploration" to "Experimental Addition1"
UPDATE4: add section "Experimental Addition2" and section "Benefits" and more examples
UPDATE5: disallow blank types as keys in a map
UPDATE6: explore more of the reasoning
UPDATE7: be more clear about return arguments of a blank function and keys in maps
UPDATE8: be more clear about unboxing and type calculations
UPDATE9: add open questions section
UPDATE10: removed generic type assertion
UPDATE11: fixed typos and disallowed interface types to be passed as blank types.
UPDATE12: add "Walk through the compilation steps" section
UPDATE13: be more clear about allowed and not allowed expressions containing blank types.
UPDATE14: tell where unboxing happens in compiler walkthrough
UPDATE15: add "non goals" section and idea of internal unbox function to "open questions"
UPDATE16: be explicit about blank types only appearing within function signatures and add
chan $A
as an allowed composed blank type.The text was updated successfully, but these errors were encountered: