Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: math: Reflect, Least, Greatest #60274

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

proposal: math: Reflect, Least, Greatest #60274

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

Comments

@jimmyfrasche
Copy link
Member

jimmyfrasche commented May 17, 2023

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.

@gopherbot gopherbot added this to the Proposal milestone May 17, 2023
@fzipp
Copy link
Contributor

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
Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Member Author

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.

@jimmyfrasche
Copy link
Member Author

jimmyfrasche commented May 18, 2023

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
}

@fzipp
Copy link
Contributor

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
Copy link
Member Author

@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
Copy link
Member Author

@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
Copy link
Member Author

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
Copy link
Member Author

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.

@zephyrtronium
Copy link
Contributor

@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
Copy link
Member Author

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
Copy link
Contributor

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
Copy link
Member Author

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

@jimmyfrasche
Copy link
Member Author

updated the proposal

@crisman
Copy link
Contributor

crisman commented Jun 6, 2023

Related NaN min/max issue in #60616

@jimmyfrasche
Copy link
Member Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

7 participants