如何在GO中实现通用类型?

发布于 2025-01-20 15:14:44 字数 954 浏览 3 评论 0原文

使用GO 1.18中的新仿制药,我认为可以创建一种可以使用的“ [a,b]”类型,以表明某些东西可能是A型或类型

。在这种情况下,函数可能会返回两个可能的值之一(例如,“正常”结果一个,一个用于错误)。

我知道错误的“惯用性”是返回“正常”值和错误值,为错误或值返回零。但是...让我感到困扰,我们实际上是在说“这是“返回 and b”的,我们真正要说的是'这是'这是'这是'这是'这返回a 或 b'。

因此,我认为也许我们可以在这里做得更好,我认为这也可能是一个很好的练习,可以看到/测试我们可以使用这些新仿制药的界限。

可悲的是,尽我所能,到目前为止,我还无法解决练习并获得任何工作/编译。从我的一次失败尝试中,这是我想以某种方式实现的界面:

//A value of type `Either[A,B]` holds one value which can be either of type A or type B.
type Either[A any, B any] interface {

    // Call either one of two functions depending on whether the value is an A or B
    // and return the result.
    Switch[R any]( // <=== ERROR: interface methods must have no type parameters
        onA func(a A) R),
        onB func(b B) R),
    ) R
}

不幸的是,这很快就失败了,因为go不允许声明此接口。显然是因为“接口方法必须没有类型参数”。

我们如何解决此限制?或者根本无法在GO中创建“类型”,以准确地表达了“这东西是/返回A或B”的想法(而不是A和B的元组)。

With the new generics in Go 1.18, I thought it might be possible to create a 'Either[A,B]' type that can be used to express that something could be either of type A or type B.

A situation where you might use this is in situations where a function might return one of two possible values as a result (e.g. one for 'normal' result and one for an error).

I know the 'idiomatic' Go for errors would be to return both a 'normal' value and an error value, returning a nil for either the error or the value. But... it sort of bothers me that we are essentially saying 'this returns A and B' in the type, where what we really mean to say is 'this returns A or B'.

So I thought maybe we can do better here, and I thought this might also be a good exercise to see/test the boundaries of what we can do with these new generics.

Sadly,try as I might, so far I have not been able solve the exercise and get anything working/compiling. From one of my failed attempts, here is an interface I'd like to implement somehow:

//A value of type `Either[A,B]` holds one value which can be either of type A or type B.
type Either[A any, B any] interface {

    // Call either one of two functions depending on whether the value is an A or B
    // and return the result.
    Switch[R any]( // <=== ERROR: interface methods must have no type parameters
        onA func(a A) R),
        onB func(b B) R),
    ) R
}

Unfortunately, this fails rather quickly because declaring this interface isn't allowed by Go. Apparantly because 'interface methods must have no type parameters'.

How do we work around this restriction? Or is there simply no way to create a 'type' in Go that accurately expresses the idea that 'this thing is/returns either A or B' (as opposed to a tuple of both A and B).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

帅气尐潴 2025-01-27 15:14:44

如果我必须这样做,我会查找一种函数式编程语言(如 OCaml)并模仿他们任一类型的解决方案。

package main

import (
    "errors"
    "fmt"
    "os"
)

type Optional[T any] interface {
    get() (T, error)
}

type None[T any] struct {
}

func (None[T]) get() (T, error) {
    var data T
    return data, errors.New("No data present in None")
}

type Some[T any] struct {
    data T
}

func (s Some[T]) get() (T, error) {
    return s.data, nil
}

func CreateNone[T any]() Optional[T] {
    return None[T]{}
}

func CreateSome[T any](data T) Optional[T] {
    return Some[T]{data}
}

type Either[A, B any] interface {
    is_left() bool
    is_right() bool
    find_left() Optional[A]
    find_right() Optional[B]
}

type Left[A, B any] struct {
    data A
}

func (l Left[A, B]) is_left() bool {
    return true
}

func (l Left[A, B]) is_right() bool {
    return false
}

func left[A, B any](data A) Either[A, B] {
    return Left[A, B]{data}
}

func (l Left[A, B]) find_left() Optional[A] {
    return CreateSome(l.data)
}

func (l Left[A, B]) find_right() Optional[B] {
    return CreateNone[B]()
}

type Right[A, B any] struct {
    data B
}

func (r Right[A, B]) is_left() bool {
    return false
}

func (r Right[A, B]) is_right() bool {
    return true
}

func right[A, B any](data B) Either[A, B] {
    return Right[A, B]{data}
}

func (r Right[A, B]) find_left() Optional[A] {
    return CreateNone[A]()
}

func (r Right[A, B]) find_right() Optional[B] {
    return CreateSome(r.data)
}

func main() {
    var e1 Either[int, string] = left[int, string](4143)
    var e2 Either[int, string] = right[int, string]("G4143")
    fmt.Println(e1)
    fmt.Println(e2)
    if e1.is_left() {
        if l, err := e1.find_left().get(); err == nil {
            fmt.Printf("The int is: %d\n", l)
        } else {
            fmt.Fprintln(os.Stderr, err)
        }
    }
    if e2.is_right() {
        if r, err := e2.find_right().get(); err == nil {
            fmt.Printf("The string is: %s\n", r)
        } else {
            fmt.Fprintln(os.Stderr, err)
        }
    }
}

If I had to do this, I would look up a functional programming language(like OCaml) and knock-off their solution of the either type..

package main

import (
    "errors"
    "fmt"
    "os"
)

type Optional[T any] interface {
    get() (T, error)
}

type None[T any] struct {
}

func (None[T]) get() (T, error) {
    var data T
    return data, errors.New("No data present in None")
}

type Some[T any] struct {
    data T
}

func (s Some[T]) get() (T, error) {
    return s.data, nil
}

func CreateNone[T any]() Optional[T] {
    return None[T]{}
}

func CreateSome[T any](data T) Optional[T] {
    return Some[T]{data}
}

type Either[A, B any] interface {
    is_left() bool
    is_right() bool
    find_left() Optional[A]
    find_right() Optional[B]
}

type Left[A, B any] struct {
    data A
}

func (l Left[A, B]) is_left() bool {
    return true
}

func (l Left[A, B]) is_right() bool {
    return false
}

func left[A, B any](data A) Either[A, B] {
    return Left[A, B]{data}
}

func (l Left[A, B]) find_left() Optional[A] {
    return CreateSome(l.data)
}

func (l Left[A, B]) find_right() Optional[B] {
    return CreateNone[B]()
}

type Right[A, B any] struct {
    data B
}

func (r Right[A, B]) is_left() bool {
    return false
}

func (r Right[A, B]) is_right() bool {
    return true
}

func right[A, B any](data B) Either[A, B] {
    return Right[A, B]{data}
}

func (r Right[A, B]) find_left() Optional[A] {
    return CreateNone[A]()
}

func (r Right[A, B]) find_right() Optional[B] {
    return CreateSome(r.data)
}

func main() {
    var e1 Either[int, string] = left[int, string](4143)
    var e2 Either[int, string] = right[int, string]("G4143")
    fmt.Println(e1)
    fmt.Println(e2)
    if e1.is_left() {
        if l, err := e1.find_left().get(); err == nil {
            fmt.Printf("The int is: %d\n", l)
        } else {
            fmt.Fprintln(os.Stderr, err)
        }
    }
    if e2.is_right() {
        if r, err := e2.find_right().get(); err == nil {
            fmt.Printf("The string is: %s\n", r)
        } else {
            fmt.Fprintln(os.Stderr, err)
        }
    }
}
梦纸 2025-01-27 15:14:44

Either 可以建模为具有 any/interface{} 类型的一个未导出字段的结构类型。类型参数将用于确保某种程度的编译时类型安全:

type Either[A, B any] struct {
    value any
}

func (e *Either[A,B]) SetA(a A) {
    e.value = a
}

func (e *Either[A,B]) SetB(b B) {
    e.value = b
}

func (e *Either[A,B]) IsA() bool {
    _, ok := e.value.(A)
    return ok
}

func (e *Either[A,B]) IsB() bool {
    _, ok := e.value.(B)
    return ok
}

如果 Switch 必须声明为方法,则不能在 R 中通过以下方式对其进行参数化:本身。附加类型参数必须在类型定义中声明,但这可能会使使用有点麻烦,因为必须在实例化时选择R

独立的函数似乎更好 - 在同一个包中,访问未导出的字段:

func Switch[A,B,R any](e *Either[A,B], onA func(A) R, onB func(B) R) R {
    switch v := e.value.(type) {
        case A:
            return onA(v)
        case B:
            return onB(v)
    }
}

带有一些代码和用法的游乐场: https://go.dev/play/p/g-NmE4KZVq2

The Either could be modeled as a struct type with one unexported field of type any/interface{}. The type parameters would be used to ensure some degree of compile-time type safety:

type Either[A, B any] struct {
    value any
}

func (e *Either[A,B]) SetA(a A) {
    e.value = a
}

func (e *Either[A,B]) SetB(b B) {
    e.value = b
}

func (e *Either[A,B]) IsA() bool {
    _, ok := e.value.(A)
    return ok
}

func (e *Either[A,B]) IsB() bool {
    _, ok := e.value.(B)
    return ok
}

If Switch has to be declared as a method, it can't be parametrized in R by itself. The additional type parameter must be declared on the type definition, however this might make usage a bit cumbersome because then R must be chosen upon instantiation.

A standalone function seems better — in the same package, to access the unexported field:

func Switch[A,B,R any](e *Either[A,B], onA func(A) R, onB func(B) R) R {
    switch v := e.value.(type) {
        case A:
            return onA(v)
        case B:
            return onB(v)
    }
}

A playground with some code and usage: https://go.dev/play/p/g-NmE4KZVq2

︶葆Ⅱㄣ 2025-01-27 15:14:44

您可以使用 https://github.com/samber/mo 库(免责声明:我是项目作者)。

任一签名是:

type Either[L any, R any] struct {}

一些示例:

import "github.com/samber/mo"

left := lo.Left[string, int]("hello")

left.LeftOrElse("world")
// hello

left.RightOrElse(1234)
// 1234

left.IsLeft()
// true

left.IsRight()
// false

您关于 Switch 模式的问题可以通过以下方式实现:

import "github.com/samber/mo"

left := lo.Left[string, int]("hello")

result := left.Match(
    func(s string) Either[string, int] {
        // <-- should enter here
        return lo.Right[string, int](1234)
    },
    func(i int) Either[string, int] {
        // <-- should not enter here
        return lo.Right[string, int](i * 42)
    },
)

result.LeftOrElse("world")
// world

result.RightOrElse(42)
// 1234

You can use the https://github.com/samber/mo library (disclaimer: I'm the project author).

Either signature is:

type Either[L any, R any] struct {}

Some examples:

import "github.com/samber/mo"

left := lo.Left[string, int]("hello")

left.LeftOrElse("world")
// hello

left.RightOrElse(1234)
// 1234

left.IsLeft()
// true

left.IsRight()
// false

Your question about a Switch pattern can be implemented this way:

import "github.com/samber/mo"

left := lo.Left[string, int]("hello")

result := left.Match(
    func(s string) Either[string, int] {
        // <-- should enter here
        return lo.Right[string, int](1234)
    },
    func(i int) Either[string, int] {
        // <-- should not enter here
        return lo.Right[string, int](i * 42)
    },
)

result.LeftOrElse("world")
// world

result.RightOrElse(42)
// 1234
独闯女儿国 2025-01-27 15:14:44

我终于找到了解决方案。关键是将“任一”类型定义为“结构”而不是接口。

type Either[A any, B any] struct {
    isA bool
    a   A
    b   B
}

func Switch[A any, B any, R any](either Either[A, B],
    onA func(a A) R,
    onB func(b B) R,
) R {
    if either.isA {
        return onA(either.a)
    } else {
        return onB(either.b)
    }
}

func MakeA[A any, B any](a A) Either[A, B] {
    var result Either[A, B]
    result.isA = true
    result.a = a
    return result
}

func MakeB[A any, B any](b B) Either[A, B] {
  ... similar to MakeA...
}

这是可行的,但代价是实际上仍然在底层使用“类元组”实现,我们同时存储 A 和 B,但确保只能使用其中之一通过公共 API。

我怀疑考虑到 Go 对我们施加的限制,这是我们能做的最好的事情。

如果有人有一个“解决方法”,本质上并不使用“元组”来表示“联合”。我认为这是一个更好的答案。

A solution finally came to me. The key was defining the 'Either' type as a 'struct' instead of an interface.

type Either[A any, B any] struct {
    isA bool
    a   A
    b   B
}

func Switch[A any, B any, R any](either Either[A, B],
    onA func(a A) R,
    onB func(b B) R,
) R {
    if either.isA {
        return onA(either.a)
    } else {
        return onB(either.b)
    }
}

func MakeA[A any, B any](a A) Either[A, B] {
    var result Either[A, B]
    result.isA = true
    result.a = a
    return result
}

func MakeB[A any, B any](b B) Either[A, B] {
  ... similar to MakeA...
}

That works, but at the 'price' of really still using a 'tuple-like' implementation under the hood were we store both an A and a B but ensure it is only possible to use one of them via the public API.

I suspect this is the best we can do given the restrictions Go puts on us.

If someone has a 'workaround' that doesn't essentially use 'tuples' to represent 'unions'. I would consider that a better answer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文