Go语言 如何为任意精度小数定义两种不同的类型,以便它们只能用于相同的类型,而不能相互赋值

fhg3lkii  于 2023-01-15  发布在  Go
关注(0)|答案(2)|浏览(118)

我当前的代码库定义了如下两种类型:

type Price uint64
type Quantity uint64

这样做效果很好,因为我不会意外地将Price类型传递给Quantity,否则编译器会抱怨。
现在我需要使用shopspring/decimal库将实现从uint64切换到任意精度的decimal。
我想从以前的uint 64实现工作的以下要求:

  • 如果我传递一个价格给一个期望数量的函数,反之亦然,编译器会抱怨
  • 我可以在两个Quantity之间进行计算(比如调用Add),而不需要任何额外的样板文件,但是对于在不同类型之间进行计算(比如将价格乘以数量),我需要通过执行一些操作(比如强制转换)来显式地允许它。
  • 我不希望有重复的代码,比如定义我想为每个类型单独使用的每个方法(即使它委托给一个公共实现)

我尝试了3种不同的实现方式,但是没有一种能正常工作。有没有什么方法能满足我的需求?如果没有,推荐的方法是什么?

方法1

type Price decimal.Decimal
type Quantity decimal.Decimal

这个实现意味着我不能在decimal上使用方法。Decimal(比如Add())用于Price类型的变量,因为根据Go语言规范,“它不继承任何绑定到给定类型的方法”。

方法2

我可以像这样使用类型别名:

type Price = decimal.Decimal
type Quantity = decimal.Decimal

但是在这种情况下我可以将一个Price传入一个期望Quantity的函数中,这样我就不会得到类型保护。一些文档说类型别名主要是在重构过程中提供帮助。

方法3

我可以尝试使用嵌入式类型:

type Quantity struct {
    decimal.Decimal
}

这在大多数情况下都有效,但在本例中:

qty.Add(qty2)

qty 2不是小数。小数所以我不得不做一些丑陋的事情

qty.Add(qty2.Decimal)
xyhw6mcr

xyhw6mcr1#

你可以在泛型中使用这种方法。对于你自己编写的类型来说,这样做更容易。如果你想用外部类型实现它,你将需要一个 Package 器。
示例:

type Number[K any] uint64

func (n Number[K]) Add(n2 Number[K]) Number[K] {
    return n + n2
}

// These are dummy types used as parameters to differentiate Number types.
type (
    price    struct{}
    quantity struct{}
)

func main() {
    var somePrice Number[price]
    var someQuantity Number[quantity]

    // no error
    somePrice.Add(somePrice)
    // cannot use someQuantity (variable of type Number[quantity]) as type Number[price] in argument to somePrice.Add
    somePrice.Add(someQuantity)
}

现在,如果你想对decimal.Decimal这样的外部类型这样做,你不能编辑它的源代码来使它这样工作,你必须为任何你需要参数类型与接收者类型协变的方法编写 Package 器。
例如,这里我假设您使用的是https://github.com/shopspring/decimal库:

package main

import "github.com/shopspring/decimal"

type Number[K any] struct{ decimal.Decimal }

// Wrapper to enforce covariant type among receiver, parameters and return.
func (n Number[K]) Add(d2 Number[K]) Number[K] {
    return Number[K]{n.Decimal.Add(d2.Decimal)}
}

// These are dummy types used as parameters to differentiate Number types.
type (
    price    struct{}
    quantity struct{}
)

func main() {
    var somePrice Number[price]
    var someQuantity Number[quantity]

    // no error
    somePrice.Add(somePrice)
    // cannot use someQuantity (variable of type Number[quantity]) as type Number[price] in argument to somePrice.Add
    somePrice.Add(someQuantity)
}

对于每个具有协变类型的方法,您都需要一个 Package 器。
或者,您可以创建自己的库或派生现有库,然后使用第一个示例中的方法直接添加此特性:
例如,decimal. go的fork可能如下所示:

//          +++++++
type Decimal[K any] struct { ... }

//             +++                +++         +++
func (d Decimal[K]) Add(d2 Decimal[K]) Decimal[K] { ... }
erhoui1w

erhoui1w2#

哎呀,stephenbez已经试过我建议的了,删除!

相关问题