Open jimmyfrasche opened 1 year ago
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?
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.
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().
If it needs a new package, that's fine by me. math does already have untyped constants for all the Min/Max values, though.
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.
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
}
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.
@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.
@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.
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.
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.
@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.
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.
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.
math.Inf(-1)
otherwise that would be <
than the result of Least
, which does not seem useful.
updated the proposal
Related NaN
min/max issue in #60616
Overlap with #50019 that proposes just Greatest/Least (under different names) and contains some useful and relevant discussion
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]()
ismath.Inf(0)
,Least[uint]()
is 0, andGreatest[int16]
ismath.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
, andComplex
have been moved into package math.I have also assumed that these additional constraints are defined:
The
Ordered
constraint is similar to that in packagecmp
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.