约束满足的3个约束浮点数组,带有方法。

huangapple go评论56阅读模式
英文:

Constraint satisfying 3-array of constraints.Float with methods

问题

我一直期待着 Go 1.18 的泛型功能,其中之一就是使用一个可以接受 float32float64 元素的三元素向量类型。现在,这已经成为可能:

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]}
}

这看起来有点冗长。真的有必要同时定义 TA 吗?

当我们要求实现 AddMul 方法时,情况变得更糟:

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)
}

多了一个额外的类型、三个泛型类型参数,并且必须给 ab 分别指定类型。进一步地,我发现在函数内部无法使表达式 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]}
}

对于另一个接口也是一样,因为您想捕获TA,但在调用点上,情况会变得更好:

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)

huangapple
  • 本文由 发表于 2022年3月16日 08:30:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/71490519.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定