golang 函数式编程库samber/mo使用: Either

如果您不了解samber/mo库, 请先阅读第一篇 Option

结构定义

有时候我们不确定值的类型, 一个值可能是int, 也可能是string, 这时候我们可以使用Either类型。 Either类型是一种表示两种可能值的类型, 和python中的 Optional类似。 结构定义如下:

type Either[L any, R any] struct {
	isLeft bool

	left  L
	right R
}

其中 isLeft表示值的类型, left和right分别表示两种可能的值。 如果isLeft为true, 则left有值, right为nil; 如果isLeft为false, 则right有值, left为nil。

构造函数

主要有一下两个:

  • mo.Left() 函数定义为 func Left[L any, R any](value L) Either[L, R] L, R分别表示两种可能的值的类型, value表示左值。 doc

  • mo.Right() 和Left类似 doc

使用示例

Either类型最常见的用法是处理错误, 正好适用于go语言。 因为go语言没有提供 try...catch 语法, 优点是错误显式处理,可以避免忘记捕获异常, 缺点是代码不够优雅。

举个例子, 如果我们用go自带的error处理, 代码如下:

package main

import (
	"errors"
	"fmt"
)

var (
	ErrRedisNotFound = errors.New("redis not found")
	ErrDBNotFound    = errors.New("db not found")
)

func readFromRedis() (string, error) {
	// let's simulate a failed operation
	return "", ErrRedisNotFound
}

func readFromDB() (string, error) {
	// let's simulate a successful operation
	return "user:1:Samber", nil
}

func main() {
	data, err := readFromRedis()
	if err != nil {
		fmt.Println("redis not found, read from db")
		data, err = readFromDB()
		if err != nil {
			fmt.Println("db not found")
		} else {
			fmt.Println("data from db is:", data)
		}
	} else {
		fmt.Println("data from redis", data)
	}
}

可以看到里面充满了if else, 代码中的逻辑结构表达得不够清晰。 使用Either处理错误的代码如下:

package main

import (
	"errors"
	"fmt"

	"github.com/samber/mo"
)

var (
	ErrRedisNotFound = errors.New("redis not found")
	ErrDBNotFound    = errors.New("db not found")
)

func readFromRedis() mo.Either[string, error] {
	// let's simulate a failed operation
	return mo.Right[string, error](ErrRedisNotFound)
}

func readFromDB() mo.Either[string, error] {
	// let's simulate a success operation
	return mo.Left[string, error]("user:1:Samber")
}

func main() {
	readFromRedis().
        Match(
			func(data string) mo.Either[string, error] {
				fmt.Println("data from redis", data)
				return mo.Left[string, error](data)
			},
			func(err error) mo.Either[string, error] {
				fmt.Println("redis not found, read from db")
				return readFromDB().
					Match(
						func(data string) mo.Either[string, error] {
							fmt.Println("data from db is:", data)
							return mo.Left[string, error](data)
						},
						func(err error) mo.Either[string, error] {
							fmt.Println("db not found")
							return mo.Right[string, error](err)
						},
					)
			},
		)
}

代码虽然更长了,但是逻辑结构很清晰

02-27 10:22