Collection of Unique Functions in Go

前端 未结 2 1332
野趣味
野趣味 2021-01-24 16:00

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 even

相关标签:
2条回答
  • 2021-01-24 16:21

    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...

    0 讨论(0)
  • 2021-01-24 16:27

    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.

    0 讨论(0)
提交回复
热议问题