目前的 Erlang 有兩種 funs。「外部」fun,Module:Name/Arity,只是一個名稱,可以自由使用。「本地」fun 包含綁定到定義它的模組中的程式碼。這表示您無法將內部 funs 儲存在資料庫中或傳送到遠端系統並期望它們正常運作。
我提議使用一種「可攜式 fun」,這是一種語法上受限的 fun。此限制確保程式設計師知道(並且執行階段可以發現)確切需要哪些模組。這些 funs 可以安全地傳送到遠端節點,並且可以安全地儲存在資料庫中,稍後檢索並執行。當它來自的模組被卸載時,持有對此類 fun 的參考的程序也不需要被終止。
為了達到最佳速度,需要一種新的方式來實作這些 funs,因此這是一個相當大的變更。然而,一個解釋可攜式函式的原型是可行的。
目前,Erlang 有
fun_expr -> 'fun' fun_clauses 'end' : ...
我們新增
fun_expr -> 'fun' '!' fun_clauses 'end' : ...
並做出以下限制
這些限制的目的是確保每次呼叫都是呼叫內建函式、已知模組的已知匯出,或是以參數形式接收的某種 fun。
內建函式 erlang:fun_info/1 在以下方面進行擴充
內建函式 erlang:fun_info/2 也以類似方式擴充。此函式提供額外的鍵 ‘source’。
內建函式和保護述詞 erlang:is_function(Term) 和 erlang:is_function(Term, Arity) 接受可攜式 funs 以及外部和本地 funs。
提供兩個新的內建函式和保護述詞 erlang:is_portable_function(Term) 和 erlang:is_portable_function(Term, Arity),它們可以識別 ‘portable’ 和 ‘external’ 函式。
(此提案絕對需要修改以使名稱更清楚。)
假設您有一個 Erlang 節點向其他節點上的客戶端報告事件。客戶端希望只接收少數事件。一種方法是讓報告器將所有事件傳送給所有客戶端,並讓客戶端進行篩選。更好的方法是讓客戶端告訴報告器他們想要哪些事件,然後讓它只傳送有趣的事件。但是客戶端如何告訴報告器他們對哪些事件感興趣呢?
一種方法是簡單地設定一組固定的事件類別。這太粗糙了。
另一種方法是定義一種事件描述語言,或許以某種方式基於匹配規範。這樣更好,但目前沒有辦法編譯匹配規範(這也是它的另一個用途!),所以匹配很慢,而且仍然有限;報告器可能想要提供篩選器可以使用的摘要函式。
另一種方法是傳送 fun,這實際上是顯而易見的做法。不幸的是,目前這無法運作,而且有不應該運作的原因。(例如,本地函式的主體可能已經進行了函式的內聯擴展,而這些函式在接收節點上的定義不同。)
另一種方法是以二進位檔形式傳送整個模組。這樣會有點笨重。它還會在報告器中產生管理可能大量模組的問題。除非報告器進行大量工作來驗證程式碼的安全性,否則它也是不安全的。從長遠來看,如果客戶端和報告器沒有使用完全相同的 BEAM(或其他 VM),也會產生版本偏差問題。
舉另一個例子,考慮將函式儲存在資料庫中。由於本地 fun 與特定模組的特定版本繫結,如果您在這個月儲存一個函式,升級您的系統,並在下個月還原模組,您不能期望它會正常運作。這表示,例如,您無法將二進位檔與知道如何解碼它的函式一起儲存。
再舉一個例子,考慮類似資料庫的東西,它會動態接收 matchspec(或類似 matchspec 的東西),並希望將其應用於數百萬筆記錄。將 matchspec 轉換為 Erlang 程式碼,甚至編譯結果都很容易,但現在您有一個要管理的模組,而不是一個可以透過垃圾回收器清理的簡單東西。
基本上,此提案的目標是讓 Erlang 在「函式是資料」的函式程式設計道路上更進一步。
然而,必須以這樣的方式來完成,即接收可攜式 fun 的程序不必完全信任來源。接收者必須能夠檢查可攜式 fun,而不僅僅是呼叫它。
僅在現有的 fun 語法之上新增可攜性限制並不是一個好主意。這會破壞大多數使用 funs 的程式。
或許最明顯的做法是使用 #fun…end,因為 sharp 似乎是 Erlang 的「哎呀,我們在美好的舊時光中沒有想到這個」標記,就像它在 Common Lisp 中一樣。然而,我們想要使用該表示法表示匿名抽象模式,而且在任何情況下,sharp 在這個內容中都不是圖示。
bang 用於表示這是一種您可能想要傳送的 fun,事實也確實如此。至於它的放置位置,bang 被認為是對 ‘fun’ 關鍵字的後修改,而不是對引數列表的前修改,因此
fun!({a,X}) -> ...
;({b,Y}) -> ...
end
沒有重複的 bang。
當您傳送可攜式 fun 時,您會傳送什麼?
如果它是原生程式碼,您就無法將 fun 從 SPARC 傳送到 Mac。如果它是 BEAM 程式碼,您就無法將 fun 傳送到另一個系統,除非它具有完全相同的 BEAM 版本。在這兩種情況下,您都讓想要檢查程式碼的謹慎接收者感到非常困難。如果它是原始程式碼,則
因此,可攜式 fun 的二進位格式將包含原始程式碼樹,可能會像 Kistler 的 Juice 一樣進行壓縮。原生表示法將包含指向 BEAM 程式碼區塊的指標,以及選擇性指向原生程式碼區塊的指標,但這些將在第一次呼叫時填入。
解釋的可能性意味著有一種廉價的方式來實作此 EEP 的原型:始終解釋。這也反對對現有 funs 進行任何更改;我們不想降低它們的速度。
“fun!” 目前是語法錯誤,因此不會影響任何現有程式碼。
當我閱讀 erlang:fun_info/[1,2] 的文件時,程式設計師應該始終將這些函式視為開放式的。現有手冊所承諾的任何內容都沒有被刪除或更改,只提供了新的值。
任何呼叫 erlang:is_portable_function/[1,2] 的現有程式都無法運作,因為沒有這樣的函式。如果模組定義了 is_portable_function/1 或 /2,則不允許在保護中使用,但允許在其他地方使用;這種模組可能會受到影響。如果編譯器在模組中發現任何函式的定義,則應列印警告訊息,並且只使用模組的版本。
無。
從長遠來看,這至少需要兩件事
一種 fun 表示法,它將指令保存在不屬於任何模組的二進位檔中,與經典的 Interlisp-D 實作類似,以便可以單獨進行垃圾回收這些 funs。無論如何,這都是理想的。
一種用於推導式的編譯策略,它像經典的 Pop-2 系統一樣,會產生內聯迴圈,而不是呼叫行外輔助函式。無論如何,這都是理想的;它應該明顯更快。
本文件已放入公共領域。