Go泛型草案设计简明指南

女主宣言

今天小编为大家分享一篇关于Golang泛型提案的最新设计草案。涉及有关为何做出某些决策的详细信息,实施细节等。希望能对大家有所帮助。

PS:丰富的一线技术、多元化的表现形式,尽在“360云计算”,点关注哦!

最近,go团队宣布了针对其go泛型提案的最新设计草案。涉及有关为何做出某些决策的详细信息,实施细节等。

因为整个草案设计内容太多了,所以本文中,我们的目标是总结即将进行的主要更改。

我们将提供一些代码片段来演示主要功能。

1

泛型函数中的类型约束

不受类型参数限制的通用函数:

// package decls, imports...

func arrayOf(type T)(elems ...T) []T {
  arr := []T{}
  for _, el := range elems {
    arr = append(arr, el)
  }
		
  return arr
}

func main() {
  strs := arrayOf("one", "two", "three", "four", "five")
  fmt.Println(strs)
	
  nums := arrayOf(1, 2, 3, 4, 5)
  fmt.Println(nums)
}

要对通用类型添加约束,可以要求实现给定的接口:

// package decls, imports...

// Person, who implements fmt.Stringer...

func join(type T fmt.Stringer)(tokens []T, delim string) string {
  res := ""
  for _, token := range tokens {
    if res != "" {
      res += delim
    }
		
    res += token.String()
  }
	
  return res
}

func main() {
  joined := join([]Person{Person{"Mike", "Jordan"}, Person{"Dave", "Stevens"}, Person{"John", "Doe"}}, ", ")
  fmt.Println(joined)
}

要指定多个类型参数,用逗号分隔:

// package decls, imports...

func mapAll(type T, R)(arr []T, mapFunc func(T) R) []R {
  res := []R{}
  for _, el := range arr {
    res = append(res, mapFunc(el))
  }
	
  return res
}

func main() {
  strs := mapAll([]int{1, 2, 3}, func(n int) string {
    return strconv.Itoa(n)
  })
	
  fmt.Println(strs)
}

对多个类型参数的约束,与编写函数参数类型相同的方式写入:

// package decls, imports...

// Person & Animal structs, which implement fmt.Stringer...

func Concat(type T1, T2 fmt.Stringer)(f T1, s T2, delim string) string {
  return f.String() + delim + s.String()
}

func main() {
  res := Concat(Person{"John", "Doe"}, Animal{"Dog", "Richie"}, " loves his ")
  fmt.Println(res)
}

以下是为两个参数指定不同类型的方法:

// package decls, imports...

// Hooman & Pet interfaces...
// Person & Dog structs...

func PlayAround(type H Hooman, P Pet)(human H, pet P) {
  fmt.Println("The human says:", human.Speak())
  fmt.Println("And the pet responds:", pet.ProduceSound())
}

func main() {
  PlayAround(Person{}, Dog{})
}

2

类型列表及可比较

可以基于一组受支持的类型来约束它们,而不是基于一组方法来约束类型。例如,可以指定接受通用类型,该通用类型只能是int或long。

这能够 利用“小于”,“大于”之类的运算符,仅适用于Go中的基本类型:

// package decls, imports...

// Ordered is a type constraint that matches any ordered type.
// An ordered type is one that supports the <, <=, >, and >= operators.
type Ordered interface {
  type int, int8, int16, int32, int64,
    uint, uint8, uint16, uint32, uint64, uintptr,
    float32, float64,
    string
}

func Max(type T Ordered)(elems []T) T {
  if len(elems) == 0 {
    var zero T
    return zero
  }
	
  max := elems[0]
  for _, el := range elems {
    if el > max {
      max = el
    }
  }
	
  return max
}

func main() {
  res := Max([]int{1, 5, 3, 10, 4})
  fmt.Println(res)
}

还具有一个称为“comparable”的现成约束,该约束类型与支持==和!=运算符的约束类型相同。

// package decls, imports...

func Contains(type T comparable)(elems []T, target T) bool {
  for _, elem := range elems {
    if elem == target {
      return true
    }
  }
	
  return false
}

func main() {
  fmt.Println(Contains([]int{1, 2, 3, 4, 5}, 4))
}

使用这些构造的接口-类型列表和/或类似列表只能用作类型约束,而不能用作函数参数。

3

通用类型

可以使用泛型类型定义结构。一旦指定了类型声明,就无需为该类型的所有函数指定类型:

// package decls, imports...

type Stack(type T) struct {
  buffer []T
}

func (v *Stack(T)) Push(elem T) {
  v.buffer = append(v.buffer, elem)
}

func (v *Stack(T)) Pop() T {
  res := v.buffer[len(v.buffer)-1]
  v.buffer = v.buffer[:len(v.buffer)-1]
	
  return res
}

func main() {
  st := &Stack(int){}
	
  st.Push(1)
  st.Push(2)	
  st.Push(3)
	
  fmt.Println(st.Pop())
  fmt.Println(st.Pop())
  fmt.Println(st.Pop())
}

也可以在接口中执行此操作。当类型约束依赖于自身时,这尤其有用。

例如。有一个T的类型约束,它需要一个Equal方法,该方法接受一个T参数:

// package decls, imports...

// Person, who implements Equaler...

type Equaler(type T) interface {
  Equal(other T) bool
}

func Contains(type T Equaler)(elems []T, target T) bool {
  for _, elem := range elems {
    if elem.Equal(target) {
      return true
    }
  }

  return false
}

func main() {
  people := []Person{Person{"Dave"}, Person{"Bob"}, Person{"Steve"}}
  fmt.Println(Contains(people, Person{"Dave"}))
}

如果需要指定具有状态修改功能的类型参数(例如,setter),则可以指定指针类型约束:

// package decls, imports...

type Setter interface {
	Set(string)
}

type Settable int

// Set sets the value of *p from a string.
func (p *Settable) Set(s string) {
  i, _ := strconv.Atoi(s)
  *p = Settable(i)
}


func FromStrings(type *T Setter)(s []string) []T {
  result := make([]T, len(s))
  for i, v := range s {
    // result[i] is an addressable value of type T,
    // so it's OK to call Set.
    result[i].Set(v)
  }	

  return result
}

func main() {
  nums := FromStrings(Settable)([]string{"1", "2", "3"})
  fmt.Println(nums)
}

注意,上面的示例如何要求明确指定将在函数中使用的类型– FromStrings(Settable)…

这是因为只要类型不作为函数参数出现,编译代码后,编译器就无法推断实际类型。因此,需要明确指定它。

总结

本文的目的是简洁明了。希望它可以帮助大家快速了解最新的泛型草案设计。

但是,围绕Go中的泛型进行任何单一设计选择,背后都有很多理由。如果有兴趣深入研究该主题,可以查阅官方设计草案文档。如果有什么疑问或建议,可以在下方留言。

360云计算

由360云平台团队打造的技术分享公众号,内容涉及数据库、大数据、微服务、容器、AIOps、IoT等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值