(如何)我可以在 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)
}
}
}
使用 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)
}
}
}