英文:
Constraint satisfying 3-array of constraints.Float with methods
问题
我一直期待着 Go 1.18 的泛型功能,其中之一就是使用一个可以接受 float32
或 float64
元素的三元素向量类型。现在,这已经成为可能:
import "golang.org/x/exp/constraints"
type Vec3[T constraints.Float] [3]T
func (vec Vec3[T]) Add(other Vec3[T]) Vec3[T] {
return Vec3[T]{vec[0] + other[0], vec[1] + other[1], vec[2] + other[2]}
}
func (vec Vec3[T]) Mul(factor T) Vec3[T] {
return Vec3[T]{vec[0] * factor, vec[1] * factor, vec[2] * factor}
}
然后我想知道相应的约束会是什么样子?忽略方法,我得到了以下解决方案:
type Vec3ish[T constraints.Float] interface {
~[3]T
}
一个接受相应参数的函数可能如下所示:
func addVecs[A Vec3ish[T], T constraints.Float](a, b A) A {
return A{a[0] + b[0], a[1] + b[1], a[2] + b[2]}
}
这看起来有点冗长。真的有必要同时定义 T
和 A
吗?
当我们要求实现 Add
和 Mul
方法时,情况变得更糟:
type Vec3ish[T constraints.Float] interface {
~[3]T
}
type Vec3ishUseful[T constraints.Float, A Vec3ish[T]] interface {
Vec3ish[T]
Add(A) A
Mul(T) A
}
func addVecsGeneric[U Vec3ishUseful[T, A], A Vec3ish[T], T constraints.Float](a U, b A) A {
return a.Add(b)
}
多了一个额外的类型、三个泛型类型参数,并且必须给 a
和 b
分别指定类型。进一步地,我发现在函数内部无法使表达式 a.Add(b).Add(b.Add(a))
正常工作。这不是我所期望的未来。在这里需要做些什么呢?
英文:
One of the things I have been looking forward to with Go 1.18's generics is to use a 3-element vector type which can take float32
or float64
for its elements. Come today, this is now possible:
import "golang.org/x/exp/constraints"
type Vec3[T constraints.Float] [3]T
func (vec Vec3[T]) Add(other Vec3[T]) Vec3[T] {
return Vec3[T]{vec[0] + other[0], vec[1] + other[1], vec[2] + other[2]}
}
func (vec Vec3[T]) Mul(factor T) Vec3[T] {
return Vec3[T]{vec[0] * factor, vec[1] * factor, vec[2] * factor}
}
I then wonder, what would an according constraint look like? Omitting the methods, I arrive at the following solution:
type Vec3ish[T constraints.Float] interface {
~[3]T
}
A function taking according arguments could look like so:
func addVecs[A Vec3ish[T], T constraints.Float](a, b A) A {
return A{a[0] + b[0], a[1] + b[1], a[2] + b[2]}
}
This seems verbose. Is it really necessary to define both T
and A
?
Things get worse when we demand Add
and Mul
to be implemented:
type Vec3ish[T constraints.Float] interface {
~[3]T
}
type Vec3ishUseful[T constraints.Float, A Vec3ish[T]] interface {
Vec3ish[T]
Add(A) A
Mul(T) A
}
func addVecsGeneric[U Vec3ishUseful[T, A], A Vec3ish[T], T constraints.Float](a U, b A) A {
return a.Add(b)
}
An extra type, three generic type parameters, and having to give a
and b
separate types. Going further, I see no way to get the expression a.Add(b).Add(b.Add(a))
to work within the function. This isn't the future I was hoping for. What needs to happen here?
答案1
得分: 1
这似乎有点冗长。真的有必要同时定义T和A吗?
是的,有必要。
您的接口Vec3ish[T constraints.Float]
本身是参数化的,因此在将其用作约束时必须进行实例化。给定类型约束A Vec3ish[T]
,您可以看到使用它的函数也是在T
中进行参数化的,并且在编译时满足Vec3ish
约束的一个T
。
函数声明可能看起来有点冗长,但在调用点上不会,因为类型推断将完成所有的工作:
v1 := Vec3[float64]{1, 2, 3}
v2 := Vec3[float64]{5, 6, 7}
result := addVecs(v1, v2)
稍微简洁一些的方法是使用矢量约束的简写形式,但如果您计划重用Vec3ish
约束,则命名接口可能更好:
func addVecs[A ~[3]T, T constraints.Float](a, b A) A {
return A{a[0] + b[0], a[1] + b[1], a[2] + b[2]}
}
对于另一个接口也是一样,因为您想捕获T
和A
,但在调用点上,情况会变得更好:
addVecsGeneric(v1, v2)
有人可能会考虑使用参数化的接口作为参数的类型,并让类型推断完成其余工作:
type Vec3ishUseful[T constraints.Float, A Vec3ish[T]] interface {
Add(A) A
Mul(T) A
}
// 使用类型参数T和A实例化Vec3ishUseful
func addVecsGeneric[A Vec3ish[T], T constraints.Float](a Vec3ishUseful[T, A], b A) A {
return a.Add(b)
}
// addVecsGeneric(v1, v2)无法编译
然而,这种方法不起作用(尚未?)。关于这个问题有一个相当广泛的讨论在这里。因此,通过这种方式,您必须放弃推断并显式传递类型参数。这在函数签名和调用者之间有点分散冗长:
addVecsGeneric[Vec3[float64], float64](v1, v2)
英文:
> This seems verbose. Is it really necessary to define both T and A?
Yes, it is.
Your interface Vec3ish[T constraints.Float]
is itself parametrized, so you must instantiate it when using it as a constraint. Given the type constraint A Vec3ish[T]
you can see that a function using it is also parametrized in T
, and one T
that fulfils the constraint in Vec3ish
at compile-time.
The function declaration might look somewhat verbose, but at the call sites it will not, since type inference will do all the legwork:
v1 := Vec3[float64]{1, 2, 3}
v2 := Vec3[float64]{5, 6, 7}
result := addVecs(v1, v2)
Slightly less verbose would be to use the shorthand for the vector constraint, but if you plan to reuse the Vec3ish
constraint, the named interface might be better:
func addVecs[A ~[3]T, T constraints.Float](a, b A) A {
return A{a[0] + b[0], a[1] + b[1], a[2] + b[2]}
}
Same goes for the other interface, since there you want to capture both T
and A
, but again, at call site, it gets better:
addVecsGeneric(v1, v2)
One might think to use the parametrized interface as type of the argument and let type inference do the rest:
type Vec3ishUseful[T constraints.Float, A Vec3ish[T]] interface {
Add(A) A
Mul(T) A
}
// Instantiating Vec3ishUseful with type params T and A
func addVecsGeneric[A Vec3ish[T], T constraints.Float](a Vec3ishUseful[T, A], b A) A {
return a.Add(b)
}
// addVecsGeneric(v1, v2) doesn't compile
However this does not work (yet?). There's a quite extensive discussion about this here. So by going this route, you have to let go of inference and pass the type parameters explicitly. This somewhat distributes verbosity between function signature and callers:
addVecsGeneric[Vec3[float64], float64](v1, v2)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论