A question about Type Sets

117 views
Skip to first unread message

Changkun Ou

unread,
Aug 23, 2021, 6:55:55 PM8/23/21
to golang-nuts
Hi golang-nuts,

I am trying out the latest type parameter and type sets design.
The purpose is to implement a Clamp function that works for numbers and vectors.

The version for numbers is straightforward and easy:

```go
// Number is a type set of numbers.
type Number interface {
~int | ~int8 | ~int32 | ~int64 | ~float32 | ~float64
}

// Clamp clamps a given value in [min, max].
func Clamp[N Number](n, min, max N) N {
if n < min { return min }
if n > max { return max }
return n
}
```

Everything is good so far. Then, let's define vector types:

```go
// Vec2 represents a 2D vector (x, y).
type Vec2[N Number] struct {
X, Y N
}

// Vec3 represents a 3D vector (x, y, z).
type Vec3[N Number] struct {
X, Y, Z N
}

// Vec4 represents homogeneous coordinates (x, y, z, w) that defines
// either a point (W=1) or a vector (W=0). Other case of W need to apply
// a perspective division to get the actual coordinates of X, Y, Z.
type Vec4[N Number] struct {
X, Y, Z, W N
}
```

However, in order to declare a type set of all possible vectors, I tried
two possibilities:

```go
// Vec is a type set of vectors.
type Vec[N Number] interface {
Vec2[N] | Vec3[N] | Vec4[N] // ERROR: interface cannot have type parameters
}
```

```go
type Vec interface {
Vec2[N Number] | Vec3[N Number] | Vec4[N Number] // ERROR: interface cannot have type parameters
}
```

Let's just enumerates all possibilities for the Vec type set:

```go
// Vec is a type set of vectors.
type Vec interface {
Vec2[float32] | Vec3[float32] | Vec4[float32] |
Vec2[float64] | Vec3[float64] | Vec4[float64]
}
```

However, with this definition, it remains very tricky to construct a
generic implementation for a clamp function:

```go
// ERROR: this function does not compile
func ClampVec[V Vec, N Number](v V, min, max N) V {
switch (interface{})(v).(type) {
case Vec2[float32]:
return Vec2[float32]{
Clamp[float32](v.X, min, max),
Clamp[float32](v.Y, min, max),
}
case Vec2[float64]:
return Vec2[float64]{
Clamp[float64](v.X, min, max),
Clamp[float64](v.Y, min, max),
}
case Vec3[float32]:
return Vec3[float32]{
Clamp[float32](v.X, min, max),
Clamp[float32](v.Y, min, max),
Clamp[float32](v.Z, min, max),
}
case Vec3[float64]:
return Vec3[float64]{
Clamp[float64](v.X, min, max),
Clamp[float64](v.Y, min, max),
Clamp[float64](v.Z, min, max),
}
case Vec4[float32]:
return Vec4[float32]{
Clamp[float32](v.X, min, max),
Clamp[float32](v.Y, min, max),
Clamp[float32](v.Z, min, max),
Clamp[float32](v.W, min, max),
}
case Vec4[float64]:
return Clamp[float64]{
Clamp[float64](v.X, min, max),
Clamp[float64](v.Y, min, max),
Clamp[float64](v.Z, min, max),
Clamp[float64](v.W, min, max),
}
default:
panic(fmt.Sprintf("unexpected type %T", v))
}
}
```

I wish I could converge to a version similar like this:

```go
func Clamp[N Number](n, min, max N) N {
if n < min { return min }
if n > max { return max }
return n
}

// ERROR: this functions does not compile
func ClampVec[N Number, V Vec[N]](v V[N], min, max N) V[N] {
switch (interface{})(v).(type) {
case Vec2[N]: // If V is Vec2[N], then return a Vec2[N].
return Vec2[N]{
Clamp[N](v.X, min, max),
Clamp[N](v.Y, min, max),
}
case Vec3[N]: // Similar
return Vec3[N]{
Clamp[N](v.X, min, max),
Clamp[N](v.Y, min, max),
Clamp[N](v.Z, min, max),
}
case Vec4[N]: // Similar
return Vec4[N]{
Clamp[N](v.X, min, max),
Clamp[N](v.Y, min, max),
Clamp[N](v.Z, min, max),
Clamp[N](v.W, min, max),
}
default:
panic(fmt.Sprintf("unexpected type %T", v))
}
}

// caller side:

Clamp[float32](256, 0, 255) // 255
Clamp[float64, Vec2[float64]]({1, 2, 3}, 0, 1) // Vec2[float32]{1, 1, 1}
...
```

I found myself trapped and not able to further proceed. Is the above code legal
with the current design but just because the compiler has not implemented it yet?
Any ideas on how could the current design be able to produce something even simpler?

Thank you in advance for your read and help.

Scott Cotton

unread,
Aug 23, 2021, 7:31:11 PM8/23/21
to golang-nuts

Great to see you playing with generics.

I'll leave it up to the experts to reply about whether the compilation problems are bugs.

But I have a suggestion:  don't try to define Vec in an interface with a type set.  Just use T[] -- looks like it might simplify things.

Scott
Reply all
Reply to author
Forward
0 new messages