如何在Go语言中实现泛型的Either类型?

qyswt5oh  于 2022-12-07  发布在  Go
关注(0)|答案(4)|浏览(271)

有了Go语言1.18中的新泛型,我认为可以创建一个'Either[A,B]'类型,用来表示某个对象可以是类型A或者类型B。
在函数可能返回两个可能值之一作为结果(例如,一个用于“正常”结果,一个用于错误)的情况下,可能会使用此选项。
我知道Go语言的“惯用"错误是同时返回一个”正常“值和一个错误值,返回一个nil值。但是......让我有点困扰的是,我们实际上是在说”this returns AandB“,而我们真正想说的是”this returns AorB“。
所以我想也许我们可以在这里做得更好,我想这也是一个很好的练习,看看/测试我们可以用这些新的泛型做什么。
遗憾的是,尽管我已经尝试过了,但到目前为止我还不能完成这个练习,也不能让任何东西工作/编译。从我的一次失败尝试中,我想 * 以某种方式 * 实现一个接口:

//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的元组)。

5tmbdcev

5tmbdcev1#

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

jogvjijk

jogvjijk2#

如果我必须这样做,我会查找一个函数式编程语言(如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)
        }
    }
}
ioekq8ef

ioekq8ef3#

我终于想到了一个解决方案。关键是将“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...
}

这是可行的,但"代价“是我们实际上仍然使用”类似元组“的实现,即我们存储AB,但确保只能通过公共API使用其中一个。
我想这是我们能做的最好的了......鉴于吴王对我们的限制。
如果有人有一个“变通办法”,基本上不使用“元组”来表示“联合”。我会认为这是一个更好的答案。

dxxyhpgq

dxxyhpgq4#

您可以使用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

相关问题