Go中的独特功能集合

时间:2017-02-09 15:33:46

标签: pointers go set

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

我已经读过,由于易于检查成员资格,地图是惯用的,因为它们很容易检查:

  while (!TryConsumeEndOfDeclaration("}", NULL)) {
    if (AtEnd()) {
      AddError("Reached end of input in method options (missing '}').");
      return false;
    }

    if (TryConsumeEndOfDeclaration(";", NULL)) {
      // empty statement; ignore
    } else {
      ...
  }

我在使用这个范例来解决问题时遇到了一些麻烦。这是我的第一次尝试:

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

很清楚为什么这不起作用:Go中的函数不是引用类型,尽管有些人会告诉你它们是。 I have proof,虽然我们不应该需要它,因为语言规范说除了map,slice和指针之外的所有内容都是按值传递的。

尝试2:

// 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)
}

这有几个问题:

  • 任何EventResponse都必须声明为func (ers EventResponseSet) Add(r *EventResponse) { // ... } ,因为您无法解决以通常方式声明的函数。

  • 你根本无法通过关闭。

  • 使用包装器不是一个选项,因为传递给包装器的任何函数都会从包装器中获取一个新地址 - 没有任何函数可以通过地址唯一标识,所有这些仔细的计划都是徒劳的。

不接受将函数定义为变量作为解决方案,这是不是很愚蠢?还有其他(好的)解决方案吗?

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

2 个答案:

答案 0 :(得分:2)

您可以使用Uvelichitel提供的reflect.Value,或fmt.Sprint()获取的stringreflect.Value.Pointer()获得的uintptr地址(更多的答案How to compare 2 functions in Go?),但我建议反对。

由于语言规范不允许compare function values,也不允许take their addresses,因此您无法保证程序中的某些功能始终有效,包括特定的运行,包括不同的(未来)Go编译器。我不会用它。

由于规范对此严格,这意味着允许编译器生成代码,例如在运行时更改函数的地址(例如,卸载未使用的函数,然后再次需要时再次加载)。我目前不知道这种行为,但这并不意味着未来的Go编译器不会利用这样的东西。

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

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

假设你需要带签名的函数:

func(p ParamType) RetType

创建一个界面:

type EventResponse interface {
    Do(p ParamType) RetType
}

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

E.g:

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,则它们相等。

     

[...和...]

     

指针值具有可比性。如果两个指针值指向同一个变量或两者的值都为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
}

结束:这可能看起来有点太麻烦,只是为了避免重复使用。我同意,我不会这样做。但如果你想......

答案 1 :(得分: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

但我认为任何文档都不保证这种行为。