在Go语言中,将一个通道的元素类型转换为接口类型的通道。

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

Passing a channel of things as a channel of interfaces in Go

问题

我的程序具有管道结构,并且我刚刚实现了一个缓存过滤器,如果已经处理过的数据版本在缓存中,则直接将数据发送到输出。

func Run(in chan downloader.ReadyDownload) chan CCFile {
    out := make(chan CCFile)
    processQueue := make(chan downloader.ReadyDownload)
    go cache.BypassFilter(in, processQueue, out)
        // 如果缓存中存在已处理的版本,则将其写入out
        // 否则将输入重定向到processQueue
    go process(processQueue, out)
    return out
}

问题是我的程序有多个类似的地方,并且许多种类型的结构体(例如此片段中的ReadyDownload和CCFile)通过通道传递。它们都实现了以下接口:

type ProcessItem interface {
    Source() string
    Target() string
    Key() string
}

因此,我的BypassFilter()函数签名如下:

func (c Cache) BypassFilter(in chan ProcessItem, process chan ProcessItem, bypass chan ProcessItem)

但是这会导致以下错误:

cannot use in (type chan downloader.ReadyDownload) as type chan utils.ProcessItem in function argument

尽管ReadyDownload确实实现了ProcessItem。例如,以下代码可以正常工作:

foo := downloader.ReadyDownload{}
var bar utils.ProcessItem
bar = foo

因此,我对Go类型和接口的理解还非常有限,所以我想问这个问题:它们是“某种东西”的通道和“其他某种东西”的通道,这使得类型不兼容吗?我应该怎么做才能使其工作?假设我有一个ReadyDownloads的通道,唯一的方法是将数据转发到一个接受interface{}类型通道的函数中,创建一个新的interface{}类型通道,将其传递给函数,并从ReadyDownloads的通道中读取数据并将其提供给另一个通道吗?

英文:

My program has a pipeline structure, and I just implemented a caching filter that sends stuff directly to output if the already processed version of data is in the cache.

func Run(in chan downloader.ReadyDownload) chan CCFile {
    out := make(chan CCFile)
    processQueue := make(chan downloader.ReadyDownload)
    go cache.BypassFilter(in, processQueue, out)
        // writes the cached, already processed version to out if it exists
        // otherwise redirects the input to processQueue
    go process(processQueue, out)
    return out
}

The problem is that my program has multiple places like this, and many kind of structs (like ReadyDownload and CCFile in this snippet) are being passed through the channels. They all implement this interface

type ProcessItem interface {
    Source() string
    Target() string
    Key() string
}

so my BypassFilter() function signature looks like this:

func (c Cache) BypassFilter(in chan ProcessItem, process chan ProcessItem, bypass chan ProcessItem)

But this brings about the following error:

cannot use in (type chan downloader.ReadyDownload) as type chan utils.ProcessItem in function argument

Although ReadyDownload certainly implements ProcessItem. For example, this works without problems:

foo := downloader.ReadyDownload{}
var bar utils.ProcessItem
bar = foo

So, my (yet) very limited understanding of Go types and interfaces brings me to ask this question: Is it the fact that they are channels of something and something else, that makes the types incompatible? What should I do to make it work? Let's say that I've got a channel of ReadyDownloads. Is the only way to forward the data to a function that takes, let's say channel of interface{}s as a parameter, to create a new channel of interface{}s, pass that to the function and read stuff from the channel of ReadyDownloads and feed them to the other channel?

答案1

得分: 6

这两个是不同的类型:

processQueue chan ReadyDownload

process chan ProcessItem

你可以将一个 ReadyDownloader 值放入类型为 chan ProcessItem 的通道中(如果它实现了该接口),但你不能将一个通道类型转换为另一个通道类型,就像你不能将 []T 切片转换为 []interface{} 切片一样,这是另一个常见的类似混淆。

你需要做的是将所有通道的类型都设置为 chan ProcessItem

func Run(in chan ProcessItem) chan CCFile {
    out := make(chan CCFile)
    processQueue := make(chan ProcessItem)
    go cache.BypassFilter(in, processQueue, out)
        // 如果存在缓存的已处理版本,则将其写入 out,否则将输入重定向到 processQueue
    go process(processQueue, out)
    return out
}

要了解更多关于为什么这样(对于切片,但对于通道也适用),你可以阅读以下 go-wiki 页面:

http://code.google.com/p/go-wiki/wiki/InterfaceSlice

英文:

These two are different types:

processQueue chan ReadyDownload

process chan ProcessItem

You can put a ReadyDownloader value in a channel of type chan ProcessItem (if it implements the interface), but you cannot convert one channel type to another, in the same way that you cannot convert a []T slice into a []interface{} slice, another common confusion similar to this one.

What you need to do is make all the channels of type chan ProcessItem:

func Run(in chan ProcessItem) chan CCFile {
    out := make(chan CCFile)
    processQueue := make(chan ProcessItem)
    go cache.BypassFilter(in, processQueue, out)
        // writes the cached, already processed version to out if it exists
        // otherwise redirects the input to processQueue
    go process(processQueue, out)
    return out
}

To read more about why this is (for slices, but the same applies for channels), you can read the following go-wiki page:

http://code.google.com/p/go-wiki/wiki/InterfaceSlice

答案2

得分: 2

将每个通道都更改为struct通道可能在这里起作用,但一般来说,您可能希望将struct类型视为处理的接口。幸运的是,Go 为我们提供了许多解决方案。这是其中之一。

考虑这个非常简单的设置,我们希望将Object结构类型用作多个接口:

// 获取对象
func ParseFile(fileName string, co chan Object) {
for _, object := range DoStuff(fileName) {
co <- object
}
}

// 使用在其他地方定义的一些保存功能:
func Archive(cs chan Saveable) {
for saveable := range cs {
saveable.Save()
}
}

type Saveable interface {
Save()
}

// 实现接口...
func (*Object) Save() {
fmt.Println("Naa, I'm lazy")
}

// 或者一些抛出功能?
func ThrowOnTheWall(ct chan Throwable) {
for throwable := range cs {
throwable.Throw()
}
}

//...

co := make(chan Object)
go ParseFile("file.xml", co)
Archive(co) // 不起作用,co的类型不正确。

在这里,到处使用chan Object是不合适的,因为您可能希望将不同于对象的东西抛到墙上(例如,您可以将其实现为Throwabletype Defecation struct {...})。

您可以使用一个Go协程在后台进行类型转换:

func ObjectToSaveable(from chan Object) chan Saveable {
to := make(chan Saveable)
go func() {
for object := range from {
to <- &object
}
close(to)
}()
return to
}

然后使用它来封装初始通道:

co := make(chan Object)
go ParseFile("file.xml", co)
Archive(ObjectToSaveable(co))

英文:

Changing every channels to struct channels might work here, but in general, you might want to treat your struct type as interfaces for processing down the road. Fortunately, go gives us many solutions. Here is one.

Consider this very simple set up, where we want to use a Object struct type as several interfaces:

// Get the objects
func ParseFile(fileName string, co chan Object) {
	for _, object := range DoStuff(fileName) {
		co &lt;- object
	}
}

// Use some saving functionality that is defined elsewhere as:
func Archive(cs chan Saveable) {
	for saveable := range cs {
		saveable.Save()
	}
}

type Saveable interface {
	Save()
}

//Implement the interfaces...
func (*Object) Save() {
	fmt.Println(&quot;Naa, I&#39;m lazy&quot;)
}

// Or some throwing functionality?
func ThrowOnTheWall(ct chan Throwable) {
	for throwable := range cs {
		throwable.Throw()
	}
}

//...

co := make(chan Object)
go ParseFile(&quot;file.xml&quot;, co)
Archive(co) // Will NOT work, co is of the wrong type.

Here, using everywhere some chan Object is not suitable, because you might want to throw on the wall something different than an object (e.g., type Defecation struct {...} that you would implement as a Throwable too.).

You could use a go routine to do the casting in the background:

func ObjectToSaveable(from chan Object) chan Saveable {
	to := make(chan Saveable)
	go func() {
		for object := range from {
			to &lt;- &amp;object
		}
        close(to)
	}()
	return to
}

And then use it to encapsulate the initial channel:

co := make(chan Object)
go ParseFile(&quot;file.xml&quot;, co)
Archive(ObjectToSaveable(co))

huangapple
  • 本文由 发表于 2014年3月25日 09:13:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/22623670.html
匿名

发表评论

匿名网友

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

确定