(如何)我可以在 go 中实现通用的“Either”类型?

(How) can I implement a generic `Either` type in go?

使用 Go 1.18 中的新泛型,我认为可以创建一个 'Either[A,B]' 类型来表示某物可以是类型 A 或类型 B。

您可能会使用它的情况是函数可能 return 两个可能值之一作为结果(例如,一个用于 'normal' 结果,一个用于错误)。

我知道 'idiomatic' Go for errors 会 return 一个 'normal' 值和一个错误值,return 为错误或价值。但是...让我有点困扰的是我们实际上是在说 'this returns A and B' 类型,而我们真正想说的是 'this returns A or B'.

所以我想也许我们可以在这里做得更好,而且我认为这也可能是一个很好的练习,可以 see/test 我们可以使用这些新泛型的边界。

遗憾的是,尽我所能,到目前为止我还没有能够解决这个问题并得到任何东西working/compiling。从我的一次失败尝试中,这是一个我想以某种方式实现的接口:

//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 不允许声明这个接口。显然是因为 'interface methods must have no type parameters'.

我们如何解决这个限制?或者根本没有办法在 Go 中创建一个 'type' 来准确表达 'this thing is/returns either A or B' 的想法(而不是 A 和 B 的元组)。

我终于想到了解决办法。关键是将 'Either' 类型定义为 'struct' 而不是接口。

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...
}

这行得通,但是在 'price' 实际上仍在使用 'tuple-like' 实现的情况下我们存储了一个 A 一个 B 但是确保只能通过 public API.

使用其中之一

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

如果某人有一个 'workaround' 本质上并不使用 'tuples' 来表示 'unions'。我认为这是一个更好的答案。

Either 可以建模为一种结构类型,其中有一个 any/interface{} 类型的未导出字段。类型参数将用于确保某种程度的 compile-time 类型安全:

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

如果我必须这样做,我会查找一种函数式编程语言(如 OCaml)和 knock-off 他们的任一类型的解决方案..

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)
        }
    }
}