2017-02-09 109 views
1

我正试图在go中实现一组函数。上下文是一个事件服务器;我想阻止(或者至少警告)为一个事件多次添加相同的处理程序。Go中唯一函数的集合

我已阅读,地图是地道的,因为容易检查会员的套使用方法:

if _, ok := set[item]; ok { 
    // don't add item 
} else { 
    // do add item 
} 

我遇到了一些麻烦,使用这种模式的功能虽然。这是我第一次尝试:

// this is not the actual signature 
type EventResponse func(args interface{}) 

type EventResponseSet map[*EventResponse]struct{} 

func (ers EventResponseSet) Add(r EventResponse) { 
    if _, ok := ers[&r]; ok { 
     // warn here 
     return 
    } 
    ers[&r] = struct{}{} 
} 

func (ers EventResponseSet) Remove(r EventResponse) { 
    // if key is not there, doesn't matter 
    delete(ers, &r) 
} 

很清楚为什么这不起作用:功能不引用类型在围棋,虽然有些人会告诉你他们。 I have proof,尽管我们不需要它,因为语言规范说除映射,切片和指针以外的所有东西都是按值传递的。

尝试2:

func (ers EventResponseSet) Add(r *EventResponse) { 
// ... 
} 

这有几个问题:

  • 任何显示eventResponse必须声明如下fn := func(args interface{}){},因为你不能满足通常的方式声明的函数。

  • 根本无法传递闭包。

  • 使用包装并不是一种选择,因为任何传递给包装的函数都会从包装中获得一个新地址 - 没有任何函数会被地址唯一标识,而所有这些仔细的计划都是徒劳的。

难道我不接受定义函数作为变量的解决方案吗?是否有另一个(好的)解决方案?

要清楚,我接受有些情况下我无法捕捉(关闭),这很好。我设想的用例是定义一堆处理程序,并且相对安全,我不会无意中将两个事件添加到同一事件两次,如果这是有道理的。

+1

如果键类型是'* EventResponse',那将无法捕获两次添加相同的函数,因为你的键是一个指向任何变量指向函数的指针。你可能想看看'reflect'包 - 这可能会提供一种方法来做你想做的事。 –

+2

函数不是[可比较的](https://golang.org/ref/spec#Comparison_operators)。您需要使用功能以外的其他功能作为关键。 –

+0

我知道这些功能没有可比性。然而,[函数指针是](http://stackoverflow.com/a/9644797/1375316),这正是我想要做的。我的问题是函数不可寻址。你能提出一个很好的替代方案吗?可以从函数本身获得的东西,而不是由用户/应用程序定义的东西? – threeve

回答

1

你可以使用reflect.Value通过Uvelichitel呈现,或函数地址由fmt.Sprint()收购了stringreflect.Value.Pointer()(更多的答案How to compare 2 functions in Go?)所获得的地址作为uintptr,但我建议反对。

由于语言规范不允许compare function values,也不允许以take their addresses,你有没有保证的东西,在你的程序时的工作永远是可行的,包括特定的运行,并包括不同的(未来)去编译器。我不会使用它。由于规范对此非常严格,这意味着编译器可以生成代码,例如在运行时更改函数的地址(例如,卸载未使用的函数,然后在再次需要时再次加载它)。我目前不知道这样的行为,但这并不意味着未来的Go编译器不会利用这样的优势。

如果您存储函数地址(以任何格式),则该值不会被视为保持函数值。如果没有其他人会“拥有”函数值,生成的代码(和Go运行时)将“自由”修改/重定位函数(从而更改其地址) - 而不违反规范,Go的类型安全性。所以你不可能对编译器感到愤怒,并且责怪编译器,但只有你自己。

如果你想检查重用,你可以使用接口值。

比方说,你需要的功能与签名:

func(p ParamType) RetType 

创建一个接口:

type EventResponse interface { 
    Do(p ParamType) RetType 
} 

例如,你可以有一个未导出struct类型,它的指针可以实现你的EventResponse接口。使导出的函数返回单个值,因此不会创建新值。

例如为:

type myEvtResp struct{} 

func (m *myEvtResp) Do(p ParamType) RetType { 
    // Your logic comes here 
} 

var single = &myEvtResp{} 

func Get() EventResponse { return single } 

是否真的需要藏在包中实现,只有创建和“发布”的单一实例?不幸的是,因为你还能创造其他价值一样&myEvtResp{}这可能仍然有同样的Do()方法不同的指针,但接口封装值可能不等于:

接口值是可比的。如果两个界面值具有identical动态类型和相同的动态值,或者两者的值均为nil,则两个界面值相等。

[...和...]

指针值是相当的。如果两个指针值指向相同的变量或者两者的值均为零,则两个指针值相等。指向不同zero-size变量的指针可能相等,也可能不相等。

类型*myEvtResp实现EventResponse,所以你可以注册它的值(唯一的价值,通过Get()访问)。您可以使用map[EventResponse]bool类型的地图,您可以在其中存储注册的处理程序,将接口值存储为键,并将true作为值存储。使用不在映射中的键索引映射会生成映射值类型的零值。因此,如果地图的值类型为bool,使用不存在的密钥对其进行索引将导致false - 告诉它不在地图中。使用已登记的EventResponse(现有密钥)进行索引将导致存储的值 - true - 告诉它在地图中,它已被注册。

你可以简单地检查,如果一个已经注册:

type EventResponseSet map[*EventResponse]bool 

func (ers EventResponseSet) Add(r EventResponse) { 
    if ers[r] { 
     // warn here 
     return 
    } 
    ers[r] = true 
} 

闭幕:这似乎有点太麻烦,只是为了避免重复利用。我同意,我不会为此而努力。但如果你想......

+0

谢谢为你的答案。我了解风险。 – threeve

+0

@threeve好的,我认为答案或多或少都是完整的。 – icza

1

你认为哪个功能相同?没有为语言规范中的函数类型定义可比性。 reflect.Value给你所期望的行为或多或少

type EventResponseSet map[reflect.Value]struct{} 
set := make(EventResponseSet) 
if _, ok := set[reflect.ValueOf(item)]; ok { 
    // don't add item 
} else { 
    // do add item 
    set[reflect.ValueOf(item)] = struct{}{} 
} 

这一说法将把通过分配唯一

//for example 
item1 := fmt.Println 
item2 := fmt.Println 
item3 := item1 
//would have all same reflect.Value 

生产作为平等的项目,但我不认为这种行为的任何文件保证。