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 cases 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, 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 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 proceed further. 

- Is the above code legal with the current design but 
because the compiler has not implemented it yet?
- Any ideas on how could the current design be able to produce something even 
simpler?
- If it is impossible to archive a similar implementation, what could be the 
best practices to implement a generic clamp function?

Thank you in advance for your read and help.

-- 
You received this message because you are subscribed to the Google Groups 
"golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to golang-nuts+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/golang-nuts/073b7b40-7505-49ae-963d-83441b9d766dn%40googlegroups.com.

Reply via email to