英文:
How to compare generic number type
问题
我有一个验证函数Positive,它能工作但看起来很丑陋。
type Positiver interface {
decimal.Decimal | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
//nolint:cyclop
func Positive[T Positiver](value T, name string, errs *[]error) {
addError := func() {
err := fmt.Errorf(`%s %w, but it's %v`, name, failures.ShouldBePositive, value)
*errs = append(*errs, err)
}
const prescision = 8
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
err := fmt.Errorf(`%s %w, but it's %s`, name, failures.ShouldBePositive, val.StringFixedBank(prescision))
*errs = append(*errs, err)
}
return
case int:
if val <= 0 {
addError()
}
case int64:
if val <= 0 {
addError()
}
case int32:
if val <= 0 {
addError()
}
case int16:
if val <= 0 {
addError()
}
case int8:
if val <= 0 {
addError()
}
case uint:
if val <= 0 {
addError()
}
case uint64:
if val <= 0 {
addError()
}
case uint32:
if val <= 0 {
addError()
}
case uint16:
if val <= 0 {
addError()
}
case uint8:
if val <= 0 {
addError()
}
case float32:
if val <= 0 {
addError()
}
case float64:
if val <= 0 {
addError()
}
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
}
我知道使用[]error
是一个不好的方法,最好是返回一个封装的错误。但这是一个兼容性问题。
我尝试像这样做:
func Positive[T Positiver](value T, name string, errs *[]error) {
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if val.(int64) < 0 {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
case float32, float64:
if val.(float64) < 0 {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
}
但这种方法给我返回了一个错误:
test panicked: interface conversion: interface {} is int, not int64
有什么更好的方法来比较值是否大于零?
英文:
I have a validation function Positive, it's works but looks ugly.
type Positiver interface {
decimal.Decimal | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | float32 | float64
}
//nolint:cyclop
func Positive[T Positiver](value T, name string, errs *[]error) {
addError := func() {
err := fmt.Errorf(`%s %w, but it's %v`, name, failures.ShouldBePositive, value)
*errs = append(*errs, err)
}
const prescision = 8
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
err := fmt.Errorf(`%s %w, but it's %s`, name, failures.ShouldBePositive, val.StringFixedBank(prescision))
*errs = append(*errs, err)
}
return
case int:
if val <= 0 {
addError()
}
case int64:
if val <= 0 {
addError()
}
case int32:
if val <= 0 {
addError()
}
case int16:
if val <= 0 {
addError()
}
case int8:
if val <= 0 {
addError()
}
case uint:
if val <= 0 {
addError()
}
case uint64:
if val <= 0 {
addError()
}
case uint32:
if val <= 0 {
addError()
}
case uint16:
if val <= 0 {
addError()
}
case uint8:
if val <= 0 {
addError()
}
case float32:
if val <= 0 {
addError()
}
case float64:
if val <= 0 {
addError()
}
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
}
I know that is bad approach to use []error, it's better just to return a wrapped error.
But it's a compatibility issue.
I tried to do like this:
func Positive[T Positiver](value T, name string, errs *[]error) {
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
if val.(int64) <= 0 {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
case float32, float64:
if val.(float64) < 0 {
err := fmt.Errorf(`%s %w, but it's not`, name, failures.ShouldBePositive)
*errs = append(*errs, err)
}
return
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
}
But this approach returns me an error:
test panicked: interface conversion: interface {} is int, not int64
What is a better way to compare that value exceeds zero?
答案1
得分: 1
你的代码在处理"interface conversion: interface {} is int, not int64"时出现问题,因为在多类型的case
中,类型切换变量val
保持其原始类型。详细信息请参见:https://stackoverflow.com/questions/40575033/golang-multiple-case-in-type-switch。
因此,在这种情况下,你确实需要断言为某种类型,以便使用比较运算符。这个"something"可以是一个类型参数。
case int, int8, int16, int32, int64:
if val.(T) <= 0 {
// ...
}
但是,这段代码仍然无法使用比较运算符,因为约束Positive
包括decimal.Decimal
,而decimal.Decimal
不支持比较运算。
尝试为decimal.Decimal
编写一个case
,并为其他数值类型编写另一个case
也不会很好,因为你没有一种好的方法来减少类型约束的类型集合。你又回到了为每种类型编写一个case
的情况。也许将来Go语言可能会允许在类型切换中使用联合约束。
你现在可以做的是静态地处理decimal.Decimal
和其他数值类型。你可以使用constraints
包中的类型来避免重新声明所有内容:Signed
、Unsigned
和Float
。然后,一个只包含数值类型的简单函数可以这样写:
func StrictlyPositive[T Signed | Unsigned | Float](v T) bool {
return v > 0
}
但是,对于浮点数,仅使用<
是不够的。浮点变量也可能是NaN或+/-无穷大。你必须决定如何排序NaN;无穷大有符号位,但我认为最好使用math.IsInf
来不隐藏在比较运算符后面的东西。
因此,总结起来,我认为这个函数最好使用反射,虽然可能会慢一些,但代码并不完全糟糕。以下是你示例的简化版本:
func CheckPositive[T Positive](value T) string {
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
return "non positive decimal"
}
case int, int8, int16, int32, int64:
if reflect.ValueOf(val).Int() <= 0 {
return "non positive signed"
}
case uint, uint8, uint16, uint32, uint64:
if reflect.ValueOf(val).Uint() == 0 {
return "non positive unsigned"
}
case float32, float64:
f := reflect.ValueOf(val).Float()
switch {
case math.IsNaN(f):
return "NaN float"
case math.IsInf(f, -1):
return "negative infinite"
case math.IsInf(f, 1):
// do nothing
default:
// not a NaN and not an Infinite
if f <= 0.0 {
return "negative float"
}
}
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
return "positive"
}
英文:
Your code doesn't work with "interface conversion: interface {} is int, not int64" because in multiple-type case
the type switch variable val
keeps its original type. See also: https://stackoverflow.com/questions/40575033/golang-multiple-case-in-type-switch for details.
So in this case you have indeed to assert to something in order to use the order operators. That "something" could be a type parameter.
case int, int8, int16, int32, int64:
if val.(T) <= 0 {
// ...
}
BUT this code still can't use the order operator because the constraint Positive
includes decimal.Decimal
, which doesn't support ordering.
Attempting to write a case
for decimal.Decimal
and a case
for other numeric types won't work well either, because you haven't a good way to reduce the type set of a type constraint. You are back writing one case
for each type. One day Go might allow using union constraints in type switches.
What you can do today is to statically handle decimal.Decimal
and other numeric types differently. You can use the types in the package constraints
to avoid redeclaring everything: Signed
, Unsigned
and Float
. Then a naive function with only numerical types is as simple as this:
func StrictlyPositive[T Signed | Unsigned | Float](v T) bool {
return v > 0
}
BUT with floats, using <
is not enough. Float variables could also be NaN or +/-infinity. You have to decide how to order NaNs; infinities have the sign bit, but IMO it's better to use math.IsInf
to not hide stuff behind the order operators.
So in conclusion I think this function is better off with reflection, which might be slower, but the code doesn't totally suck. The following is a simplified version of your example:
func CheckPositive[T Positive](value T) string {
switch val := any(value).(type) {
case decimal.Decimal:
if val.IsNegative() || val.IsZero() {
return "non positive decimal"
}
case int, int8, int16, int32, int64:
if reflect.ValueOf(val).Int() <= 0 {
return "non positive signed"
}
case uint, uint8, uint16, uint32, uint64:
if reflect.ValueOf(val).Uint() == 0 {
return "non positive unsigned"
}
case float32, float64:
f := reflect.ValueOf(val).Float()
switch {
case math.IsNaN(f):
return "NaN float"
case math.IsInf(f, -1):
return "negative infinite"
case math.IsInf(f, 1):
// do nothing
default:
// not a NaN and not an Infinite
if f <= 0.0 {
return "negative float"
}
}
default:
panic(fmt.Sprintf(`%T is not supported type`, val))
}
return "positive"
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论