在Go语言中实现通用映射器的惯用方式是什么?

huangapple go评论111阅读模式
英文:

Idiomatic way to implement generic mappers in Go

问题

假设我想编写一个函数来检查切片中的元素是否满足谓词条件:

  1. func IsIn(array []T, pred func(elt T) bool) bool {
  2. for _, obj := range array {
  3. if pred(obj) {
  4. return true
  5. }
  6. }
  7. return false
  8. }

显然,上述代码无法编译,因为T不存在。我可以用interface{}替换它,像这样:

  1. func IsIn(array []interface{}, pred func(elt interface{}) bool) bool {
  2. ...
  3. }

因为我愿意让谓词执行类型转换:

  1. IsIn([]interface{}{1,2,3,4}, func(o interface{}) {return o.(int) == 3; })

但是,该函数将不接受任何不是[]interface{}类型的数组:

  1. IsIn([]int{1,2,3,4}, func(o interface{}) { return o.(int) == 3; }) // 无法编译

类似地:

  1. func IsIn(arr interface, pred func(o interface{}) bool) bool {
  2. for _, o := range arr.([]interface{}) { ... }
  3. }
  4. IsIn([]int{1,2,3,4}, func(o interface{}) { return o.(int) == 3; }) // 运行时发生错误(无法将[]int转换为[]interface)

另一种选择是为每种数组类型编写具有类型的函数:

  1. IsInInt(arr []int, pred func(i int) bool) { ... }
  2. IsInStr(arr []string, pred func(s string) bool) { ... }
  3. ...

但这似乎会导致大量的代码重复。

是否有人提出了一种优雅的方法来处理这种情况?

编辑

感谢jnml在Go反射方面的出色提示,我认为我已经找到了一种很好的表达这些模式的方法,即将每个“可迭代”对象转换为通道:

  1. func iter(obj interface{}) chan interface{} {
  2. c := make(chan interface{})
  3. v := reflect.ValueOf(obj)
  4. if (v.Kind() == reflect.Array || v.Kind() == reflect.Slice) {
  5. go func() {
  6. for i := 0; i < v.Len(); i++ {
  7. c <- v.Index(i).Interface()
  8. }
  9. close(c)
  10. }()
  11. } else if v.Kind() == reflect.Chan {
  12. go func() {
  13. x, ok := v.Recv()
  14. for ok {
  15. c <- x.Interface()
  16. x, ok = v.Recv()
  17. }
  18. close(c)
  19. }()
  20. } else if (... 你拥有的任何迭代协议 ...) {
  21. } else {
  22. panic("无法迭代!")
  23. }
  24. return c
  25. }

使用它重写我的初始示例在playground上

非常感谢jnmlANisus的帮助!

英文:

Let's say I want to write a function to check whether a predicate is matched for an element in a slice:

  1. func IsIn(array []T, pred func(elt T) bool) bool {
  2. for _, obj := range array {
  3. if pred(obj) { return true;}
  4. }
  5. return false;
  6. }

Obviously, the previous code won't compile, since T does not exist. I can replace it with some interface{} like this:

  1. func IsIn(array[]interface{}, pred func(elt interface{}) bool) bool {
  2. ...
  3. }

As I am happy to let the predicate perform the casting:

  1. IsIn([]interface{}{1,2,3,4}, func(o interface{}) {return o.(int) == 3; });

But then, the function won't accept any array which is not of type []interface{}:

  1. IsIn([]int{1,2,3,4}, func(o interface{}) { return o.(int) == 3; }) // DO NOT COMPILE

And similarly:

  1. func IsIn(arr interface, pred func(o interface{}) bool) bool {
  2. for _, o := range arr.([]interface{}) { ... }
  3. }
  4. IsIn([]int{1,2,3,4}, func(o interface{}) { return o.(int) == 3; }) // PANICS AT RUNTIME (cannot cast []int to []interface)

The other alternative is to have typed functions for each array type:

  1. IsInInt(arr []int, pred func(i int) bool) { ... }
  2. IsInStr(arr []string, pred func(s string) bool) { ... }
  3. ...

But it seems like a LOT of code duplication.

Has anyone come up with an nice way to deal with such situations ?

EDIT

Thanks to jnml's fantastic tips on Go reflection, I think I have found a nice way to express these patterns, by converting every 'iterable' to a channel:

  1. func iter(obj interface{}) chan interface{} {
  2. c := make(chan interface{})
  3. v := reflect.ValueOf(obj)
  4. if (v.Kind() == reflect.Array || v.Kind() == reflect.Slice) {
  5. go func() {
  6. for i := 0; i &lt; v.Len(); i++ {
  7. c&lt;-v.Index(i).Interface()
  8. }
  9. close(c)
  10. }()
  11. } else if v.Kind() == reflect.Chan {
  12. go func() {
  13. x, ok := v.Recv()
  14. for ok {
  15. c&lt;-x.Interface()
  16. x,ok = v.Recv()
  17. }
  18. close(c)
  19. }()
  20. } else if (... whatever iteration protocol you have ...) {
  21. } else {
  22. panic(&quot;Cannot iterate !&quot;)
  23. }
  24. return c;
  25. }

With my initial example rewritten using it on the playground.

Thanks a lot to jnml and ANisus for helping out !

答案1

得分: 4

例如:

  1. package main
  2. import (
  3. "fmt"
  4. "reflect"
  5. )
  6. func checkSlice(slice interface{}, predicate func(reflect.Value) bool) bool {
  7. v := reflect.ValueOf(slice)
  8. if v.Kind() != reflect.Slice {
  9. panic("not a slice")
  10. }
  11. for i := 0; i < v.Len(); i++ {
  12. if predicate(v.Index(i)) {
  13. return true
  14. }
  15. }
  16. return false
  17. }
  18. func main() {
  19. a := []int{1, 2, 3, 4, 42, 278, 314}
  20. fmt.Println(checkSlice(a, func(v reflect.Value) bool { return v.Int() == 42 }))
  21. b := []float64{1.2, 3.4, -2.5}
  22. fmt.Println(checkSlice(b, func(v reflect.Value) bool { return v.Float() > 4 }))
  23. }

Playground


输出:

  1. true
  2. false
英文:

For example:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;reflect&quot;
  5. )
  6. func checkSlice(slice interface{}, predicate func(reflect.Value) bool) bool {
  7. v := reflect.ValueOf(slice)
  8. if v.Kind() != reflect.Slice {
  9. panic(&quot;not a slice&quot;)
  10. }
  11. for i := 0; i &lt; v.Len(); i++ {
  12. if predicate(v.Index(i)) {
  13. return true
  14. }
  15. }
  16. return false
  17. }
  18. func main() {
  19. a := []int{1, 2, 3, 4, 42, 278, 314}
  20. fmt.Println(checkSlice(a, func(v reflect.Value) bool { return v.Int() == 42 }))
  21. b := []float64{1.2, 3.4, -2.5}
  22. fmt.Println(checkSlice(b, func(v reflect.Value) bool { return v.Float() &gt; 4 }))
  23. }

Playground


Output:

  1. true
  2. false

答案2

得分: 3

我不能说这是惯用的方法,但一个惯用的解决方案是像sort包中那样为数组定义一个接口:

  1. type Interface interface {
  2. Len() int
  3. Equal(i int, v interface{}) bool
  4. }
  5. func IsIn(array Interface, value interface{}) bool {
  6. for i := 0; i < array.Len(); i++ {
  7. if array.Equal(i, value) {
  8. return true
  9. }
  10. }
  11. return false;
  12. }

只要你的数组实现了这个接口,就可以使用IsIn()函数。

你可以在这里找到一个可运行的示例。

英文:

I can't say if it is the most idiomatic. But one idiomatic solution would be to do like in the sort package; to define an interface for the array:

  1. type Interface interface {
  2. Len() int
  3. Equal(i int, v interface{}) bool
  4. }
  5. func IsIn(array Interface, value interface{}) bool {
  6. for i := 0; i &lt; array.Len(); i++ {
  7. if array.Equal(i, value) {
  8. return true
  9. }
  10. }
  11. return false;
  12. }

As long as your array implements this interface, you can use IsIn().

Working example can he found here

huangapple
  • 本文由 发表于 2013年8月7日 18:59:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/18101638.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定