Skip to content
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: spec: permit using var.(type) when instantiating a generic function #58608

Closed
boobusy opened this issue Feb 20, 2023 · 22 comments
Closed
Labels
generics Issue is related to generics Proposal
Milestone

Comments

@boobusy
Copy link

boobusy commented Feb 20, 2023

type Demo struct{}
func UseFunc[T any]() {}

var value = new(Demo)
UseFunc[value.(type)]()

build error: invalid AST: use of .(type) outside type switch.

Hope to support value.(type) in generics code.

@gopherbot gopherbot added this to the Proposal milestone Feb 20, 2023
@DeedleFake
Copy link

This seems like an extension of #34515 to add support for explicit generic instantiations, too.

@ianlancetaylor ianlancetaylor changed the title proposal: better use of generic type proposal: spec: permit using var.(type) when instantiating a generic function Feb 21, 2023
@ianlancetaylor
Copy link
Contributor

CC @griesemer

Can you give an example where this would be useful? Thanks.

@go101
Copy link

go101 commented Feb 21, 2023

Sometimes this feature provides a convenience that we don't need to import the package in which the value type is declared.

@go101
Copy link

go101 commented Feb 21, 2023

Another benefit is that this feature will generalize the vaue.(type) syntax so that it will be not a bizarre existence any more.

@DeedleFake
Copy link

It would help with the irregularity some, but a type switch with a variable would still be irregular. A regular switch with a variable looks like

switch v := f(); v {
  // ...
}

while a type switch with a variable looks like

switch v := i.(type) {
  // ...
}

@go101
Copy link

go101 commented Feb 22, 2023

switch v := i.(type) {
  // ...
}

may be viewed as a short-from of

switch v, T := i.(type); T {
  // ...
}

@aarzilli
Copy link
Contributor

Would this only be allowed if value has a non-interface type?

func g[T any]() {
...
}

func f(x any) {
    g[x.(type)]()
}

@adonovan
Copy link
Member

I can imagine this is theoretically useful when you want to instantiate f[T] with a type T that you are not allowed to spell out because it involves the name of an unexported type from another package. But that seems pretty rare: usually f has parameters or results that are sufficient for type inference to do its thing, so UseFunc, which has neither, is quite unusual.

Can you provide a realistic example?

@apparentlymart
Copy link

My experience with type switches and type assertions lead me to assume that the value.(type) here would be inspecting the dynamic type of value, but I can see from the example that this isn't true because value is of a struct type, not of an interface type.

I'd worry about making it ambiguous whether .(type) refers to the static type or the dynamic type of a value depending on context. Aside from being potentially confusing, it seems like an ambiguity hazard for future evolution of the language where in some contexts it might be important to distinguish dynamic type vs. static type.

I'd prefer to see a different syntax for writing "the static type of this value", so that it's clear how future language features might support both static and dynamic types in the same position, and so that a reader can clearly see which of the two a particular expression is referring to without having detailed knowledge of the language specification. However, I must admit I don't have a specific proposal for that; so far I can't think of any other situation where it's been necessary to express "the static type of this value", so I can't find precedent to copy.

@DeedleFake
Copy link

DeedleFake commented Feb 22, 2023

so far I can't think of any other situation where it's been necessary to express "the static type of this value", so I can't find precedent to copy.

A couple of possibly useful situations off the top of my head:

type Example struct { m map[int]string }
var ex Example
ex.m = make(ex.m.(type))

var ex2 ex.(type) // Might be stretching it a bit.

func F[T any]() T { ... }
v = F[v.(type)]() // 'twould be nice if this was automatic, to be honest.

That first one has its own proposal already, #34515. Also worth noting that the last one won't help in situations where the result is being returned, despite the type having been declared elsewhere already.

@ianlancetaylor
Copy link
Contributor

I agree with @apparentlymart that the syntax v.(type) means to obtain the dynamic type of a value of interface type. The goal here seems to be to use the static type of the variable. In particular, consider this variant of the original example:

type Demo interface { M() }
type MyM struct{}
func (MyM) M() {}

func UseFunc[T any]() {}

var value = M(MyM)
UseFunc[value.(type)]()

Here value.(type) is a valid expression that returns a value of type MyM. Are we supposed to instantiate UseFunc with M or MyM? We could make up an answer, but I hope we can agree that the code is not clear.

What this proposal is after is more like the typeof operator that is part of the GNU extension to C (and may be part of the upcoming C23 standard). See https://gcc.gnu.org/onlinedocs/gcc/Typeof.html .

@boobusy
Copy link
Author

boobusy commented Feb 23, 2023

是否仅在具有非接口类型时才允许这样做?value

func g[T any]() {
...
}

func f(x any) {
    g[x.(type)]()
}

yes, x is a static type. only static types are allowed

@boobusy
Copy link
Author

boobusy commented Feb 23, 2023

我可以想象,当您想使用不允许拼写的类型 T 进行实例化时,这在理论上很有用,因为它涉及来自另一个包的未导出类型的名称。但这似乎非常罕见:通常 f 具有足以让类型推断完成其操作的参数或结果,因此两者都没有的 UseFunc 非常不寻常。f[T]

你能提供一个现实的例子吗?

我同意@apparentlymart语法意味着获取接口类型值的动态类型。这里的目标似乎是使用变量的静态类型。特别是,请考虑原始示例的此变体:v.(type)

type Demo interface { M() }
type MyM struct{}
func (MyM) M() {}

func UseFunc[T any]() {}

var value = M(MyM)
UseFunc[value.(type)]()

下面是返回类型值的有效表达式。我们应该用 or 实例化吗?我们可以编造一个答案,但我希望我们能同意代码不清楚。value.(type)``MyM``UseFunc``M``MyM

这个提议所追求的更像是作为 GNU 扩展到 C 的一部分(并且可能是即将推出的 C23 标准的一部分)的运算符。请参阅 https://gcc.gnu.org/onlinedocs/gcc/Typeof.htmltypeof

It only needs to be consistent with the quality of reflection.TypeOf () But the return MyM type is not a reflect.Type.

@go101
Copy link

go101 commented Feb 24, 2023

How about using v.type to get the type of v? And using v.(type) to get the concrete type of v?

[edit]: They only have different meaning when v is an interface.

[edit 2]: Or, in v.(type), v must be an interface and v.(type) can only evaluated at run time.

@DeedleFake
Copy link

Actually, by analogy with assertions and conversions, how about type(v)?

@adonovan
Copy link
Member

Actually, by analogy with assertions and conversions, how about type(v)?

It creates parsing ambiguity. A statement that starts with type is, today, a type declaration statement. But if type(v) was a type then you could write type(v).f(x) or type(v)(x).y() or type(v){...}.f().

@DeedleFake
Copy link

It could be avoided by always assuming type to be a declaration when used in a statement context. That would make things like type(v).f(x) a compile-time error which would be somewhat inconsistent, but I can't see being able to do that being remotely useful anyways.

@apparentlymart
Copy link

Part of my reservation about lightly-overloading the .(type) syntax was that the inconsistency might inhibit future evolution of the language if a particular feature needs to be able to support both referring to static types and dynamic types.

This type(v) syntax seems like it might have a similar hazard, but I will concede that I'm only pondering hypotheticals and I don't have a concrete example.

One specific concrete decision to make, though, is that I think this type(v) syntax (with the exception that it "prefers to be" a type declaration when there's ambiguity) explicitly rules out being able to use it in the RecieverType part of a method expression, which I assume is what the examples above like type(v).f and type(v).f(x) were intended to represent.

I suppose though that it would be possible in principle to still express the non-declaration form by placing type(v) in parentheses: (type(v)).f(x). So while the non-orthogonality is undesirable, it does at least still leave some options open for future language features.

@DeedleFake
Copy link

One specific concrete decision to make, though, is that I think this type(v) syntax (with the exception that it "prefers to be" a type declaration when there's ambiguity) explicitly rules out being able to use it in the RecieverType part of a method expression, which I assume is what the examples above like type(v).f and type(v).f(x) were intended to represent.

It should only be a problem in situations where it's ambiguous if it's an expression or a statement. It's notable that regardless of that ambiguity,

type(v)

on its own line would be a compile-time error anyways because it would be an unused expression that isn't a function call, though, interestingly, it would be one that's entirely a compile-time directive and produces no actual run-time code. That's why the method expression syntax is a problem: It allows you to both use it in an expression context and call a function.

It might actually be less of a problem than it seems, though. I don't know how possible it would be for the parser to see type(v).m(x) and realize that that statement is comprised of a single expression and thus treat the type usage as being the expression-based one, not a declaration. An actual type declaration, at least as it stands before accepting this proposal, doesn't contain any expressions at all, after all.

@adonovan adonovan added the generics Issue is related to generics label Apr 12, 2023
@rsc
Copy link
Contributor

rsc commented Nov 29, 2023

As Ian said, this is a compile-type typeof like C's typeof, which we shouldn't use x.(type) syntax for, since that's a dynamic type.

That aside, it's unclear that we should add typeof(V) to the language. For this specific use case, instead of writing

func UseFunc[T any]() {}
UseFunc[typeof(value)]()

you can always write

func UseFuncByValue[T any](v T) { UseFunc[T]() }
UseFuncByValue(value)

And 99% of the time UseFunc is going to take the value already - generic functions that don't use the types in the argument list are rare - so you won't need that wrapper in the first place.

@rsc
Copy link
Contributor

rsc commented Dec 4, 2023

Based on the discussion above, this proposal seems like a likely decline.
— rsc for the proposal review group

@rsc
Copy link
Contributor

rsc commented Dec 14, 2023

No change in consensus, so declined.
— rsc for the proposal review group

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
generics Issue is related to generics Proposal
Projects
Status: Declined
Development

No branches or pull requests

9 participants