前言
大伙都知道在今年的3月份,Go官方在1.18版本中推出了泛型的特性。本文就是介绍一个基于1.18版本的Go工具类库samber/lo
,该库相对于其他使用反射来实现的库来说,更加的快、同时还安全。
它提供了切片的许多辅助函数。例如:Filter
、Slice
、Fill
、Map
、FilterMap
、FlatMap
、GroupBy
、PartitionBy
等,还提供了类似Java中的try-catch机制的异常处理函数,例如:Try
、TryWithErrorValue
、TryCatch
等
install
go get github.com/samber/lo@v1
import
1 2 3 4
| import ( "github.com/samber/lo" lop "github.com/samber/lo/parallel" )
|
通常我们只导入github.com/samber/lo
就足够了,lop
是一个使用协程并发执行的模块,里面只包含了Map
、ForEach
、Times
、GroupBy
、PartitionBy
这五个并行函数。
使用
Slice的辅助函数们
Filter
1 2 3 4 5 6
| func testFilter() { strs := lo.Filter[string]([]string{"hello", "good bye", "world", "fuck", "fuck who"}, func(s string, _ int) bool { return !strings.Contains(s, "fuck") }) fmt.Println(strs) //[hello good bye world] }
|
Filter
函数的第一个参数就是元素类型切片[]V
,第二个就是过滤函数func(V, int) bool
,其中V就是所定义泛型类型,int为切片下标。
Map
1 2 3 4 5 6 7
| func testMap() { intNums := lo.Map[string, int]([]string{"1", "2", "3"}, func(s string, _ int) int { num, _ := strconv.Atoi(s) return num }) fmt.Println(intNums) // [1 2 3] }
|
Map
函数用于将一种切片类型转换为另一种类型,例如上面的例子就是将string
切片转换为int
切片。
FlatMap
1 2 3 4 5 6 7 8 9 10
| func testFlatMap() { flatMap := lo.FlatMap[int64, string]([]int64{1, 8, 16, 20}, func(i int64, _ int) []string { return []string{ strconv.FormatInt(i, 8), strconv.FormatInt(i, 10), strconv.FormatInt(i, 16), } }) fmt.Println(flatMap) // [1 1 1 10 8 8 20 16 10 24 20 14] }
|
FlatMap
函数将一种切片类型转换为另一种切片类型同时将其扁平化
GroupBy
1 2 3 4 5 6 7
| func testGroupBy() { group := lo.GroupBy[int, int]([]int{1, 2, 3, 4, 5, 6, 7, 8}, func(i int) int { return i % 3 }) fmt.Println(group) // map[0:[3 6] 1:[1 4 7] 2:[2 5 8]]
}
|
GroupBy
函数将切片类型按照分组函数分组,返回一个map类型的数据。
PartitionBy
1 2 3 4 5 6 7 8 9 10 11 12 13
| func testPartitionBy() { partitionBy := lo.PartitionBy[int, string]([]int{-3, -2, -1, 0, 1, 2, 3}, func(i int) string { if i < 0 { return "negative" } else if i > 0 { return "positive" } else { return "zero" } }) fmt.Println(partitionBy) // [[-3 -2 -1] [0] [1 2 3]]
}
|
PartitionBy
将一个切片分组成二维切片,类似于GroupBy
函数只不过一个返回是切片、一个是map类型
Count
1 2 3 4
| func testCount() { count := lo.Count([]int{1, 4, 7, 5, 3, 1}, 1) fmt.Println(count) // 2 }
|
Count
函数计算切片中等于第二个参数的元素的数量。
CountBy
1 2 3 4 5 6
| func testCountBy() { count := lo.CountBy([]int{1, 4, 7, 5, 3, 1}, func(i int) bool { return i > 5 }) fmt.Println(count) // 1 }
|
CountBy
函数计算比较函数为true的元素的数量
Map的辅助函数
Keys
1 2 3 4 5
| func testKeys() { keys := lo.Keys[string, int](map[string]int{"hello": 1, "world": 2}) fmt.Println(keys) // [hello world]
}
|
Keys
函数返回map的key数组
Values
1 2 3 4 5
| func testValues() { values := lo.Values[string, int](map[string]int{"hello": 1, "world": 2}) fmt.Println(values) // [1 2]
}
|
Values
函数返回map的value数组
异常处理的辅助函数
Must
1 2 3 4 5 6 7
| func testMust() { num := lo.Must(strconv.Atoi("1")) fmt.Println(num) //1 num = lo.Must(strconv.Atoi("Hello World")) fmt.Println(num) // panic: strconv.Atoi: parsing "Hello World": invalid syntax
}
|
Must
函数通常用来包裹一个函数调用,如果它的第二个参数值为error
或者false
的话则会抛出panic
,否则返回第一个参数值。
下面是Must
函数的源码,可以看到就只是判断err参数是否为bool类型的false值或者为error类型,如果是则抛出panic。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| func Must[T any](val T, err any, messageArgs ...interface{}) T { must(err, messageArgs...) return val }
// must panics if err is error or false. func must(err any, messageArgs ...interface{}) { if err == nil { return }
switch e := err.(type) { case bool: if !e { message := messageFromMsgAndArgs(messageArgs...) if message == "" { message = "not ok" }
panic(message) }
case error: message := messageFromMsgAndArgs(messageArgs...) if message != "" { panic(message + ": " + e.Error()) } else { panic(e.Error()) }
default: panic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error") } }
|
Must函数还有不同参数的变种函数,不过行为是一致的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| func example0() (error) func example1() (int, error) func example2() (int, string, error) func example3() (int, string, time.Date, error) func example4() (int, string, time.Date, bool, error) func example5() (int, string, time.Date, bool, float64, error) func example6() (int, string, time.Date, bool, float64, byte, error)
lo.Must0(example0()) val1 := lo.Must1(example1()) // alias to Must val1, val2 := lo.Must2(example2()) val1, val2, val3 := lo.Must3(example3()) val1, val2, val3, val4 := lo.Must4(example4()) val1, val2, val3, val4, val5 := lo.Must5(example5()) val1, val2, val3, val4, val5, val6 := lo.Must6(example6())
|
Try
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| func testTry() { ok := lo.Try(func() error { fmt.Println("尝试工作") panic("下班啦") }) fmt.Println(ok) // false
ok = lo.Try(func() error { fmt.Println("尝试工作") fmt.Println("结束工作") return nil }) fmt.Println(ok) // true }
|
Try
函数当返回值不为nil或者函数抛出panic时则会返回false,否则返回true
TryWithErrorValue
1 2 3 4 5 6 7 8 9 10
| func testTryWithErrorValue() { err, ok := lo.TryWithErrorValue(func() error { fmt.Println("尝试工作") panic("下班啦") }) fmt.Println(err, ok) // 尝试工作 // 下班啦 false
}
|
TryWithErrorValue
函数于Try函数逻辑是一样的,只不过多个painc的返回值
1 2 3 4 5 6 7 8 9
| func testTryCatch() { flag := false lo.TryCatch(func() error { panic(flag) }, func() { flag = true }) fmt.Println(flag) // true }
|
同Try函数相同的逻辑,只不过当出现panic时会调用第二个函数参数
小结
本文只是列举了一些samber/lo
框架中常用的函数,samber/lo
还提供了很多的辅助函数,例如对字符串、元组、搜索、条件判断都提供了相关的函数。同时通过上面列举的一些例子就可以发现该工具类框架确实可以减轻开发时的一些负担,让我们在crud时更顺手,如果还有还要了解更多的工具类函数就去官网看看吧~
官网链接