/ go Public

# proposal: math: Reflect, Least, Greatest#60274

Open
opened this issue May 17, 2023 · 19 comments
Open

# proposal: math: Reflect, Least, Greatest #60274

opened this issue May 17, 2023 · 19 comments
Labels
Milestone

 ```package math func Reflect[T Number]() Kind type Kind uint func (Kind) Size() int func (Kind) Ordered() bool func (Kind) Float() bool func (Kind) Complex() bool func (Kind) Signed() bool func (Kind) Unsigned() bool func (Kind) Integer() bool func Least[T Ordered]() T func Greatest[T Ordered]() T func Convert[S, T Number](T) (S, bool)``` Reflect returns a Kind bitset that provides information about the properties of a numeric type. The bool returning methods correspond with the constraints and Size returns 8, 16, etc. Since this is about the properties of the type not its identity `Reflect[int]() == Reflect[int64]()` on platforms where int is 64 bits. Least (Greatest) return the element that is `<=` (`>=`) every element in T. For example, `Greatest[float64]()` is `math.Inf(0)`, `Least[uint]()` is 0, and `Greatest[int16]` is `math.MaxInt16`. Convert performs the conversion between numeric types S and T and the boolean reports whether the conversion was lossless, returning false if there was any truncation or wrapping. Future versions of the language may allow `Reflect` to be written using type switches but this would still provide a more convenient interface. For convenience, I've assumed #52427 is accepted and that `Unsigned`, `Signed`, `Integer`, `Float`, and `Complex` have been moved into package math. I have also assumed that these additional constraints are defined: ```type Ordered Integer | Float type Number Ordered | Complex``` The `Ordered` constraint is similar to that in package `cmp` except that it does not include `~string`. The package qualification should be sufficient to disambiguate. If this api is too much or too different for math, it, and all the constraints, could go into a new package, `math/number` or the like. The text was updated successfully, but these errors were encountered:
added this to the Proposal milestone May 17, 2023

### fzipp commented May 18, 2023

 It's possible to get this information with real reflect but that is heavyweight. Why would this one be less heavyweight? What would it do differently?

### jimmyfrasche commented May 18, 2023

 Unlike `reflect.Type`, `math.Type` would only have finitely many values. With language support the body would probably look something like: ```switch T.(type) { case ~uint8: return uint8Type``` Even if it needs to resort to skullduggery and reach-into-the-runtime or teach-the-compiler-a-trick magic to work around the current inability to write that in Go itself, I'd imagine that it would still retain the essence of being a simple lookup.

### earthboundkid commented May 18, 2023

 At this point, math contains almost all float64 stuff. I think if there's a package with generic math stuff, it should go in a new package, and the old math package can be considered implicitly "math/float64s". Maybe math/numeric.Types/Reflect()/Min()/Max().

### jimmyfrasche commented May 18, 2023

 If it needs a new package, that's fine by me. math does already have untyped constants for all the Min/Max values, though.

### zephyrtronium commented May 18, 2023

 I created https://github.com/zephyrtronium/number as a demonstration of the proposed `Reflect` function. It does perform faster than reflection, but unless there is some reason to believe the layout of internal/abi.Type will change in the future, I don't think it needs to be in the standard library.

### jimmyfrasche commented May 18, 2023

 First, super cool! Second, I do think even if the abi remains stable I'd feel uneasy relying on a third party dep making those kind of assumptions and playing those kinds of shenanigans. Third, I think regardless of implementation having it part of the stdlib is worthwhile to have it in a centralized place, especially one that can be updated in lockstep if something like, for example 128 bit ints get added to the language.

 Here's a small example of use. It provides a fully correct generic variadic min. It works correctly even if `len(vs) == 0` and uses `math.Min` instead of `<` for floating point types: ```func Min[T notComplex](vs ...T) T { min := math.Greatest[T]() if math.Reflect[T]().Float() { // T = ~float32 | ~float64 // so these are always lossless conversions min := float64(min) for _, v := range vs { min = math.Min(min, float64(v)) } return T(min) } for _, v := range vs { if v < min { min = v } } return min }```

mentioned this issue May 18, 2023

### fzipp commented May 18, 2023

 With language support the body would probably look something like: ```switch T.(type) { case ~uint8: return uint8Type``` You can't switch on a generic type like that. If you're proposing a language change ("With language support ...") you should say so.

### jimmyfrasche commented May 18, 2023

 @fzipp I was saying that IF there were such a language change the code would, then, in that hypothetical scenario, just be a big ole switch.

### jimmyfrasche commented May 18, 2023

 @zephyrtronium a more concrete argument against relying on the internals is that alternate Go implementations may not use the same representation internally but if it's in std each such implementation can wire it in appropriately.

### jimmyfrasche commented May 19, 2023

 If it does get its own package, it could hold the various number-y constraints, too. Something like: ```package number type Signed ~int | int8 | ⋯ type Unsigned ~uint | uint8 | ⋯ type Integer Signed | Unsigned type Float ~float32 | ~float64 type Ordered Integer | Float type Complex ~complex64 | ~complex128 type Type Ordered | Complex type Kind uint // A bunch of methods on Kind func Reflect[T Type]() Kind func Least[T Ordered]() T func Greatest[T Ordered]() T``` That gives you a lot of what you need to write generic numeric algorithms in one place without filling math up with a bunch of definitions.

### jimmyfrasche commented May 19, 2023

 A nice to have would be (using the definition of Type in the previous post, not the one in the first post) ```func Convert[S, T Type](T) (v S, lossless bool) func CanConvert[S, T Type](T) (lossless bool)``` that returns true if converting back to `T` produces something `==` to the original value and false if there was any rounding/overflow/etc. That's easy to check by doing the reverse conversion but it's awkward and doesn't read well, imo.

mentioned this issue May 19, 2023

### zephyrtronium commented May 19, 2023

 @jimmyfrasche I feel like the actual proposal here is drifting substantially, so I can't tell whether I'm +1. It might be to everyone's benefit to update the title and first post with exactly the API you're suggesting.

### jimmyfrasche commented May 19, 2023

 Sorry. Reflect/Least/Greatest are the main thing. I'm fine for those going in math. If they go in their own package then that package could be a good place to put the numeric constraints as well. The Convert/CanConvert functions are semi-related functionality that seem like a good fit with Reflect/Least/Greatest, wherever those end up. I'll think about how best to clear that all up.

### golightlyb commented May 19, 2023

 Can you clarify, for a float, is Least math.Inf(-1) or minus math.MaxFloat32/64? I don't think it matters too much which, just think it should be documented.

### jimmyfrasche commented May 19, 2023

 `math.Inf(-1)` otherwise that would be `<` than the result of `Least`, which does not seem useful.

### jimmyfrasche commented May 20, 2023

 updated the proposal

### crisman commented Jun 6, 2023

 Related `NaN` min/max issue in #60616

### jimmyfrasche commented Jun 21, 2023

 Overlap with #50019 that proposes just Greatest/Least (under different names) and contains some useful and relevant discussion

mentioned this issue Aug 9, 2023
mentioned this issue May 1, 2024
mentioned this issue May 19, 2024