2017-02-09 67 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 

生產作爲平等的項目,但我不認爲這種行爲的任何文件保證。