使用空接口在Go语言中泛化并发映射函数

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

Generalizing Concurrent Map Function with Empty Interface In GOlang

问题

所以我对Golang非常陌生,考虑到该语言周围的热点似乎是并发性,我想找一个好方法来入门,就决定编写一个通用的映射函数。伪代码如下:

map(F 函数, A 数组){
    return([F(k) for k in A])
}

显然,我希望每个 F(k) 的计算都是并发进行的。为了组织代码,我有一个主文件包含我的实现,还有一个支持文件 Mr 包含我所需的定义。

.
├── main.go
└── Mr
     └── Mr.go

Main 是一个简单的测试实现,它应该将一个字符串数组转换为一个整数数组,其中结果的每个成员是输入数组中相应字符串的长度。

package main

import(
    "fmt"
    "./Mr"
)

func exfunc(i int, c chan int){
    c<-i
}

func main(){
    data := make(map[int]string)
    data[1]="these"
    data[2]="are"
    data[3]="some"
    data[4]="words"
    data[5]="and a few more..."
    f := func(w string)int{
        return(len(w))
    }
    testMr := Mr.Map(f,data) // 这是第 22 行(稍后会有影响)
    fmt.Println(testMr)
}

在我明确指定所有类型的情况下,这与我的 Mr.Map 函数非常配合使用。

package Mr

type result struct{
    key,value int
}

func wrapper(f func(string) int,k int,v string, c chan result){
    c <- result{k,f(v)}
}

func Map(f func(string) int,m map[int]string) map[int]int{
    c := make(chan result)
    ret := make(map[int]int)
    n := 0
    for k := range m{
        go wrapper(f,k,m[k],c)
        n++
    }
    for ;n>0; {
        r := <-c
        ret[r.key]=r.value
        n--
    }
    return(ret)
}

然而,我希望能够使用空接口来泛化这个映射函数。

package Mr

type T interface{}

type result struct{
    key,value T
}

func wrapper(f func(T) T,k T,v T, c chan result){
    c <- result{k,f(v)}
}

func Map(f func(T) T,m map[T]T) map[T]T{
    c := make(chan result)
    ret := make(map[T]T)
    n := 0
    for k := range m{
        go wrapper(f,k,m[k],c)
        n++
    }
    for ;n>0; {
        r := <-c
        ret[r.key]=r.value
        n--
    }
    return(ret)
}

不幸的是,当我使用这个泛化的 Mr.Map 运行 main 函数时,会出现以下错误。

# command-line-arguments
./main.go:22: cannot use f (type func(string) int) as type func(Mr.T) Mr.T in argument to Mr.Map
./main.go:22: cannot use data (type map[int]string) as type map[Mr.T]Mr.T in argument to Mr.Map

所以,显然我理解错误的原因,但是我觉得为每种可能的键和值类型组合重新编写 Map 函数太过繁琐。

"这里有没有解决方法,还是这就是这门语言的特性?"

英文:

So I'm super new to Golang and seeing as the big buzz around the language seems to be concurrency I figured a good way to get my toes wet would be to write a generalized map function. In psudo code:

map(F funtion,A array){
    return([F(k) for k in A])
}

And obviously I want the calculations for each F(k) occur concurrently. For organization I have a main file with my implementation and a supporting file Mr with my required definitions.

.
├── main.go
└── Mr
 &#160;&#160; └── Mr.go

Main is a simple test implementation which should convert an array of strings to an array of ints where each member of the result is the length of the corresponding string in the input array.

package main

import(
    &quot;fmt&quot;
    &quot;./Mr&quot;
)

func exfunc(i int, c chan int){
    c&lt;-i
}

func main(){
    data := make(map[int]string)
    data[1]=&quot;these&quot;
    data[2]=&quot;are&quot;
    data[3]=&quot;some&quot;
    data[4]=&quot;words&quot;
    data[5]=&quot;and a few more...&quot;
    f := func(w string)int{
        return(len(w))
    }
    testMr := Mr.Map(f,data) // this is line 22 (matters later)
    fmt.Println(testMr)
}

Which works great with my Mr.Map function given that I specify all the types explicitly.

package Mr

type result struct{
    key,value int
}

func wrapper(f func(string) int,k int,v string, c chan result){
    c &lt;- result{k,f(v)}
}

func Map(f func(string) int,m map[int]string) map[int]int{
    c := make(chan result)
    ret := make(map[int]int)
    n := 0
    for k := range m{
        go wrapper(f,k,m[k],c)
        n++
    }
    for ;n&gt;0; {
        r := &lt;-c
        ret[r.key]=r.value
        n--
    }
    return(ret)
}

However I was hoping that I would be able to generalize this mapping with the empty interface.

package Mr

type T interface{}

type result struct{
    key,value T
}

func wrapper(f func(T) T,k T,v T, c chan result){
    c &lt;- result{k,f(v)}
}

func Map(f func(T) T,m map[T]T) map[T]T{
    c := make(chan result)
    ret := make(map[T]T)
    n := 0
    for k := range m{
        go wrapper(f,k,m[k],c)
        n++
    }
    for ;n&gt;0; {
        r := &lt;-c
        ret[r.key]=r.value
        n--
    }
    return(ret)
}

Unfortunately when I run main with this generalize Mr.Map I get the following error.

# command-line-arguments
./main.go:22: cannot use f (type func(string) int) as type func(Mr.T) Mr.T in argument to Mr.Map
./main.go:22: cannot use data (type map[int]string) as type map[Mr.T]Mr.T in argument to Mr.Map

So yeah, obviously I understand what the errors are telling me but it seems wild that I would have to re-write my Map function for each possible combination of key and value types.

Is there a work around here, or is this just the nature of the beast?

答案1

得分: 1

没有真正的解决办法,这就是问题的本质。

这种语言是在与C++斗争一段时间后设计的,创作者的想法是简化所有非关键的东西,同时增加关键的功能,使语言更具表达力。

你可以在这里阅读一些他们的思考过程,即使你不同意他们所做的所有决定,我认为这也是相当有趣的:

https://commandcenter.blogspot.com.ar/2012/06/less-is-exponentially-more.html

在你的例子中,如果你愿意,你可以让你的映射和函数使用interface{}(顺便说一下,它被称为空接口,而不是“nil”接口)。

但是当然,你将失去编译时类型检查,并且必须在各个地方添加类型转换。

你还可以尝试找到一个接口来表达你想要使用的类型的共同点(这可能并不容易,甚至可能根本不可能),然后围绕该接口构建你的映射API。

英文:

No real workaround there, that's the nature of the beast.

The language was designed after struggling with C++ for some time, and the idea of the creators was simplifying all non-vital things but at the same time make key additions to make the language more expressive.

You can read a bit about their reasoning here, which I believe is quite interesting even if you don't agree with all the decisions they made:

https://commandcenter.blogspot.com.ar/2012/06/less-is-exponentially-more.html

In your example, if you wanted to, you could make your maps and functions use interface{} (which by the way is called the empty interface and not "nil" interface).

But of course you would lose compile-time type checking and would have to add casts all around.

You can also try to find an interface to express the commonalities of the types you want to use (which might not be so easy or even possible at all), and then build your mapping API around that interface.

答案2

得分: 1

Go语言的哲学与流行的动态语言不兼容,这些动态语言通常使用泛型函数的风格。为了正确告知编译器你的意图,你应该通过接口或者为每种类型编写映射函数来表达你所需的映射。

映射需要分配一个数组,在集合中进行迭代,并为集合中的每个元素添加一些数据元素到数组中。如果你需要为结构体切片创建一个映射,这在应用层中很常见,你可以在Go中简洁地表达这个映射:

https://play.golang.org/p/pk3Tl_BdlD

动态语言构建了一个“类型树”,其中包含了“通用”类型,可以通过一个符号调用诸如map之类的函数,适用于任何可能的类型。这提供了很多开发者的生产力,因为代码可以松散地编写,以便进行轻松的实验。

Go语言设计用于编写半永久性的软件。它的性能很好,因为它需要向编译器提供更多的信息。映射只需要大约三行代码,所以在开发者生产力与效率之间的成本/效益比方面,Go更偏向于性能。像映射、归约和过滤这样的函数应该根据需要显式地编写。

为了评估这门语言,我鼓励你尝试使用Go编写一个程序来解决一个问题,看看会有什么结果。

英文:

The philosophy of Go is not compatible with generalized functions such as is the style with popular dynamic languages. To properly inform the compiler of what you are trying to do, you should express your needed map through an interface or simply by writing it for each type you are using it with.

Mapping requires allocating an array, iterating through a collection, and adding to the array some data element for each element in the collection. If you need a map for a slice of structs, as is common in the application layer, you can express this tersely in Go:

https://play.golang.org/p/pk3Tl_BdlD

Dynamic languages build a "type tree" of "generic" types that allow for terse programming, such as functions like map being called by one symbol over any possible type. This provides a ton of developer productivity because code can be written loosely to allow easy experimentation.

Go is designed for writing semi-permanent software. It performs well because it requires more information to be supplied to the compiler. Map is only about three lines of code, so the cost/benefit of developer productivity v. efficiency lands on the performance side for Go. Functions like map, reduce and filter should be written explicitly as needed.

To evaluate the language, I would encourage you to try to solve a problem with a Go program and see where that takes you.

huangapple
  • 本文由 发表于 2017年7月6日 07:35:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/44937567.html
匿名

发表评论

匿名网友

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

确定