drbxr86044 2017-02-09 15:33
浏览 66
已采纳

Go中独特功能的集合

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.

  • 写回答

2条回答 默认 最新

  • dongsha7215 2017-02-09 21:32
    关注

    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 How to compare 2 functions in Go?), 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...

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 用verilog实现tanh函数和softplus函数
  • ¥15 求京东批量付款能替代天诚
  • ¥15 slaris 系统断电后,重新开机后一直自动重启
  • ¥15 51寻迹小车定点寻迹
  • ¥15 谁能帮我看看这拒稿理由啥意思啊阿啊
  • ¥15 关于vue2中methods使用call修改this指向的问题
  • ¥15 idea自动补全键位冲突
  • ¥15 请教一下写代码,代码好难
  • ¥15 iis10中如何阻止别人网站重定向到我的网站
  • ¥15 滑块验证码移动速度不一致问题