Go语言中的独特功能集合

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

Collection of Unique Functions in Go

问题

我正在尝试在Go语言中实现一组函数。背景是一个事件服务器;我想要防止(或至少警告)为同一个事件多次添加相同的处理程序。

我了解到,使用映射作为集合是惯用的,因为可以轻松检查成员资格:

if _, ok := set[item]; ok {
    // 不要添加item
} else {
    // 添加item
}

然而,对于函数来说,我在使用这种范式时遇到了一些问题。以下是我的第一次尝试:

// 这不是实际的签名
type EventResponse func(args interface{})

type EventResponseSet map[*EventResponse]struct{}

func (ers EventResponseSet) Add(r EventResponse) {
    if _, ok := ers[&r]; ok {
        // 在这里发出警告
        return
    }
    ers[&r] = struct{}{}
}

func (ers EventResponseSet) Remove(r EventResponse) {
    // 如果键不存在,不会有影响
    delete(ers, &r)
}

很明显,这种方法不起作用:在Go语言中,函数不是引用类型,尽管有些人会告诉你它们是。我有证据,尽管我们不需要它,因为语言规范说除了映射、切片和指针之外的所有内容都是按值传递的。

第二次尝试:

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

这种方法有几个问题:

  • 任何EventResponse都必须像fn := func(args interface{}){}这样声明,因为无法对通常方式声明的函数取地址。
  • 无法传递闭包。
  • 使用包装器不是一个选项,因为传递给包装器的任何函数都将从包装器获得一个新的地址,没有任何函数可以通过地址唯一地标识,所有这些仔细的规划都是徒劳的。

我是否愚蠢地不接受将函数定义为变量的解决方案?是否有另一个(好的)解决方案?

明确地说,我接受有些情况我无法捕捉到(闭包),这是可以接受的。我设想的用例是定义一堆处理程序,并相对安全地确保我不会意外地将同一个处理程序添加到同一个事件中,如果这样说的话。

英文:

I am trying to implement a set of functions in go. The context is an event server; I would like to prevent (or at least warn) adding the same handler more than once for an event.

I have read that maps are idiomatic to use as sets because of the ease of checking for membership:

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

I'm having some trouble with using this paradigm for functions though. Here is my first attempt:

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

It is clear why this doesn't work: functions are not reference types in Go, though some people will tell you they are. I have proof, though we shouldn't need it since the language specification says that everything other than maps, slices, and pointers are passed by value.

Attempt 2:

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

This has a couple of problems:

  • Any EventResponse has to be declared like fn := func(args interface{}){} because you can't address functions declared in the usual manner.

  • You can't pass a closure at all.

  • Using a wrapper is not an option because any function passed to the wrapper will get a new address from the wrapper - no function will be uniquely identifiable by address, and all this careful planning is for nought.

Is it silly of me to not accept defining functions as variables as a solution? Is there another (good) solution?

To be clear, I accept that there are cases that I can't catch (closures), and that's fine. The use case that I envision is defining a bunch of handlers and being relatively safe that I won't accidentally add one to the same event twice, if that makes sense.

答案1

得分: 6

你可以使用Uvelichitel提供的reflect.Value,或者使用fmt.Sprint()获取的函数地址作为string,或者使用reflect.Value.Pointer()获取的uintptr类型的地址(更多信息请参考https://stackoverflow.com/questions/34901307/how-to-compare-2-functions-in-go/34901677#34901677的答案),但我不建议这样做。

由于语言规范不允许比较函数值,也不允许获取它们的地址,你无法保证在程序中某个时刻有效的代码将始终有效,包括特定的运行和不同(未来的)Go编译器。我不建议使用它。

由于规范对此有严格要求,这意味着编译器可以生成在运行时更改函数地址的代码(例如,卸载未使用的函数,然后在以后需要时再次加载)。我目前不知道是否存在这样的行为,但这并不意味着未来的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 {
    // 在这里编写你的逻辑
}

var single = &myEvtResp{}

func Get() EventResponse { return single }

是否真的需要将实现隐藏在一个包中,只创建和“发布”一个单例?不幸的是,是的,因为否则你可以创建其他值,比如&myEvtResp{},它们可能是不同的指针,但仍具有相同的Do()方法,但接口包装值可能不相等:

接口值是可比较的。如果它们具有相同的动态类型和相等的动态值,或者两者都具有值nil,则两个接口值相等。

[...并且...]

指针值是可比较的。如果它们指向同一个变量,或者两者都具有值nil,则两个指针值相等。指向不同零大小变量的指针可能相等,也可能不相等。

类型*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] {
        // 在这里发出警告
        return
    }
    ers[r] = true
}

**总结:**这可能看起来有点麻烦,只是为了避免重复使用。我同意,我不会选择这种方法。但如果你想要...

英文:

You could use reflect.Value presented by Uvelichitel, or the function address as a string acquired by fmt.Sprint() or the address as uintptr acquired by reflect.Value.Pointer() (more in the answer https://stackoverflow.com/questions/34901307/how-to-compare-2-functions-in-go/34901677#34901677), but I recommend against it.

Since the language spec does not allow to compare function values, nor does it allow to take their addresses, you have no guarantee that something that works at a time in your program will work always, including a specific run, and including different (future) Go compilers. I would not use it.

Since the spec is strict about this, this means compilers are allowed to generate code that would for example change the address of a function at runtime (e.g. unload an unused function, then load it again later if needed again). I don't know about such behavior currently, but this doesn't mean that a future Go compiler will not take advantage of such thing.

If you store a function address (in whatever format), that value does not count as keeping the function value anymore. And if no one else would "own" the function value anymore, the generated code (and the Go runtime) would be "free" to modify / relocate the function (and thus changing its address) – without violating the spec and Go's type safety. So you could not be rightfully angry at and blame the compiler, but only yourself.

If you want to check against reusing, you could work with interface values.

Let's say you need functions with signature:

func(p ParamType) RetType

Create an interface:

type EventResponse interface {
    Do(p ParamType) RetType
}

For example, you could have an unexported struct type, and a pointer to it could implement your EventResponse interface. Make an exported function to return the single value, so no new values may be created.

E.g.:

type myEvtResp struct{}

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

var single = &myEvtResp{}

func Get() EventResponse { return single }

Is it really needed to hide the implementation in a package, and only create and "publish" a single instance? Unfortunately yes, because else you could create other value like &myEvtResp{} which may be different pointers still having the same Do() method, but the interface wrapper values might not be equal:

> Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.
>
> [...and...]
>
> Pointer values are comparable. Two pointer values are equal if they point to the same variable or if both have value nil. Pointers to distinct zero-size variables may or may not be equal.

The type *myEvtResp implements EventResponse and so you can register a value of it (the only value, accessible via Get()). You can have a map of type map[EventResponse]bool in which you may store your registered handlers, the interface values as keys, and true as values. Indexing a map with a key that is not in the map yields the zero value of the value type of the map. So if the value type of the map is bool, indexing it with a non-existing key will result in false – telling it's not in the map. Indexing with an already registered EventResponse (an existing key) will result in the stored value – true – telling it's in the map, it's already registered.

You can simply check if one already been registered:

type EventResponseSet map[*EventResponse]bool

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

Closing: This may seem a little too much hassle just to avoid duplicated use. I agree, and I wouldn't go for it. But if you want to...

答案2

得分: 1

你指的是要相等的函数是哪些?语言规范中没有定义函数类型的可比性。reflect.Value 可以提供你所需的行为。

type EventResponseSet map[reflect.Value]struct{}
set := make(EventResponseSet)
if _, ok := set[reflect.ValueOf(item)]; ok {
    // 不添加 item
} else {
    // 添加 item
    set[reflect.ValueOf(item)] = struct{}{}
}

这个断言只会将通过赋值产生的相等项视为相等。

例如:

item1 := fmt.Println
item2 := fmt.Println
item3 := item1

这三个变量的 reflect.Value 都是相同的。

但我不认为这种行为在任何文档中有保证。

英文:

Which functions you mean to be equal? Comparability is not defined for functions types in language specification. reflect.Value gives you the desired behaviour more or less

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{}{}
}

this assertion will treat as equal items produced by assignments only

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

but I don't think this behaviour guaranteed by any documentation.

huangapple
  • 本文由 发表于 2017年2月9日 23:33:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/42140758.html
匿名

发表评论

匿名网友

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

确定