Golang泛型之comparable

5,431 阅读1分钟

概念

comparable是golang新引入的预定义标识符,是一个接口,指代可以使用==或!=来进行比较的类型集合。

应用场景

comparable仅能用于泛型中的类型限定(type constraint)。

可直接作为类型限定使用,也可嵌入到类型限定中使用。

定义泛型Dictionary

golang spec对map的key有如下约束:

  • key类型必须定义比较操作符==和!=;
  • key类型必须不能是function、map或slice(没有定义比较操作符);
  • 对于interface类型,其动态类型必须定义比较操作符;
  • 不满足上述约束,则会导致运行时异常(run-time panic)。

在golang中map被定义为一种由键(key)及索引值(value)构成的元素集合,实质上与python中Dictionary表达的是同一个东西。考虑到函数式编程中经常使用map来表示映射,在有些场景下我们可以通过将golang中的map重定义为Dictionary来减少概念上的混淆。

package main

import "fmt"

type Dictionay[K comparable, V any] map[K]V

func main() {
	dict := Dictionay[string, int]{"string": 1}
	fmt.Printf("dict: %#v \n", dict)
}
$ go run main.go 
dict: main.Dictionay[string,int]{"string":1} 

定义泛型Set

Golang并未提供内置的Set类型,不过一般我们可以利用map的key的唯一性来自定义Set类型。

package sets

// Set is a set of elements
type Set[T comparable] map[T]struct{}

// NewSet returns a set of elements with assigned type
func NewSet[T comparable](es ...T) Set[T] {
	s := Set[T]{}
	for _, e := range es {
		s.Add(e)
	}
	return s
}

// Len report the elements number of s
func (s *Set[T]) Len() int {
	return len(*s)
}

// IsEmpty report wether s is empty
func (s *Set[T]) IsEmpty() bool {
	return s.Len() == 0
}

// Add add elements to set s
// if element is already in s this has no effect
func (s *Set[T]) Add(es ...T) {
	for _, e := range es {
		(*s)[e] = struct{}{}
	}
}

// Remove remove elements from set s
// if element is not in s this has no effect
func (s *Set[T]) Remove(es ...T) {
	for _, e := range es {
		delete(*s, e)
	}
}

// Contains report wether v is in s
func (s *Set[T]) Contains(v T) bool {
	_, ok := (*s)[v] 
	return ok
}

// Clone create a new set with the same elements as s
func (s *Set[T]) Clone() Set[T] {
	r := Set[T]{}
	r.Add(s.ToSlice()...)
	return r
}

// ToSlice transform set to slice
func (s *Set[T]) ToSlice() []T {
	r := make([]T, 0, s.Len())

	for e := range *s {
		r = append(r, e)
	}

	return r
}
package sets

// Union the union of some sets(eg. A, B)
// is the set of elements that are in either A or B
func Union[T comparable](sets ...Set[T]) Set[T] {
	r := NewSet[T]()
	for _, s := range sets {
		r.Add(s.ToSlice()...)
	}
	return r
}

相应样例及运行结果如下:

package main

import (
	"fmt"

	"github.com/skholee/generic-wheel/sets"
)

func main() {
	s := sets.NewSet("a", "b", "c")
	s.Add("c", "d", "e")
	s.Remove("b", "d")
	fmt.Printf("set: %v \n", s.ToSlice())

	s = sets.Union(s, sets.NewSet("e", "f"), sets.NewSet("a", "f"))
	fmt.Printf("set: %v \n", s.ToSlice())

	s = sets.Intersect(s, sets.NewSet("a", "b", "c", "f", "g"))
	fmt.Printf("set: %v \n", s.ToSlice())

	s = sets.Difference(s, sets.NewSet("c", "f", "d"))
	fmt.Printf("set: %v \n", s.ToSlice())
}
$ go run .
set: [c e a] 
set: [a c e f] 
set: [c f a] 
set: [a] 

后记

通过自定义泛型Dictionary及Set,我们基本对golang泛型中的comparable类型限定有了一个大体的了解,同时也为后续进行golang泛型编程提供了一些基本可用的轮子。

完整代码见:github.com/skholee/gen…

References

Golang泛型提案:go.googlesource.com/proposal/+/…