以下 Ruby 方法的正確 RBS 描述是什么:
def insert( id:, **options )
# ...
'Hello World'
end
假設以下用法:
insert( id: 123, a: 'A', b: 'B' )
uj5u.com熱心網友回復:
TL;博士
RBS 語法并不總是有助于告訴您在給定任意呼叫的情況下簽名可以接受什么。它只是告訴您簽名實際定義的內容,以及方法可能回傳的內容。因此,嘗試根據您使用簽名的方式而不是實際的簽名定義來定義 RBS 結構可能不會產生您期望的結果。
使用 RBS 或 TypeProf 生成簽名
蘇格蘭皇家銀行
由于您發布的 #insert 方法未定義為類的一部分,因此 RBS 將假定它是 Object 的一部分。因此,如果您將原始帖子的內容放入foo.rb并呼叫rbs prototype rb foo.rb,則 RBS 解釋的方法的簽名將是:
class Object
def insert: (id: untyped, **untyped options) -> "Hello World"
end
請注意,RBS v2.5.0 非常聰明,它清楚地告訴您您尚未定義關鍵字的型別,也沒有定義**options關鍵字引數將接受:id的名稱或值的型別。那是因為您沒有將它們定義為簽名本身的一部分。
但是,它也知道該方法回傳一個具有已知值的字串,并告訴您。
TypeProf
TypeProf v0.21.2 的作業方式略有不同,對簽名的解釋也略有不同。僅給出上述方法,它將回傳:
# Classes
class Object
private
def insert: (id: untyped, **untyped) -> String
end
主要區別在于它認為這個方法是私有的,只是告訴你它回傳一個字串而不是注釋它當前回傳的特定字串。但是,如果您擴展 foo.rb 以包含您的方法呼叫以及方法定義,如下所示:
def insert(id:, **options)
'Hello World'
end
insert(id: 123, a: 'A', b: 'B')
然后 TypeProf 將嘗試根據對該方法的實際呼叫來推斷型別:
# Classes
class Object
private
def insert: (id: Integer, **String) -> String
end
這里的主要區別在于 TypeProf 使用單個提供的示例來說明您如何呼叫該方法來推斷預期的型別:id和收集的關鍵字引數預期采用的方式。在您發布的示例中,:id采用整數,您收集的關鍵字引數都是字串物件,并且該方法仍然回傳一個字串,盡管 TypeProf 不假定該字串的內容rbs prototype。
如果您以多種方式呼叫您的方法,rbs prototype則不會更改,但 TypeProf 將根據其各種呼叫者傳遞的型別調整 rbs 簽名的推斷型別。例如,如果您使用不同的引數兩次呼叫 #insert 方法:
insert id: 123, a: 'A', b: 'B'
insert id: 'foo', a: 1, b: [], c: {}
現在 TypeProf 認為簽名是:
# Classes
class Object
private
def insert: (id: Integer | String, **Array[untyped] | Hash[untyped, untyped] | Integer | String) -> String
end
TypeProf 現在認為它:id可以期望 Integer 或 String,并且您當前收集的關鍵字選項可以采用 Array、Hash 和 Integer 值。
兩者都是正確的
從語法的角度來看,所有三個結果都是有效的 RBS。作為作者,您可以決定哪種工具最能代表您的意圖,或者您是否需要調整 RBS 語法以更準確地反映您對簽名的期望。
此外,每個工具都有不同的操作和輸出模式,并以不同的方式解釋 Ruby 代碼。因此,只要它們都可以輸出可以使用 RBS 語法驗證工具驗證的有效 RBS 語法,您就不能真正將其視為比另一個更好。
鑒于您的具體示例,我認為rbs prototype rb給出了更簡單和更準確的結果,但 TypeProf 可能會提供更多您似乎期望 RBS 語法提供的內容(特別是顯式或鴨式簽名),而無需進行手動更改到 RBS 語法檔案。
顯式簽名產生更顯式的 RBS
當然,給定更明確的簽名將有助于 rbs 和 TypeProf。例如,在以下代碼上以與上面相同的方式運行它們,并使用更明確的方法定義,可以讓您在語法上更加具體:
def insert(id: 1, a: '', b: '')
'Hello World'
end
insert id: 123, a: 'A', b: 'B'
# rbs prototype rb foo.rb
class Object
def insert: (?id: ::Integer, ?a: ::String, ?b: ::String) -> "Hello World"
end
# typeprof foo.rb
class Object
private
def insert: (?id: Integer, ?a: String, ?b: String) -> String
end
因為您的方法簽名現在更加明確,兩者rbs prototype現在都typeprof回傳大致相同的結果。您的簽名也被更清楚地記錄下來,因為您明確定義了預期的關鍵字引數,而不是將其留給未知關鍵字引數的集合。
目前,RBS 語法并不打算取代像@param這樣的 YARD 標簽。如果您希望在沒有強制執行的情況下獲得該級別的檔案,請使用 YARD。如果您想強制執行簽名合約或非鴨子型別,那么您需要使用 RBS 語法來驗證您的代碼并使用 Steep、Sorbet 或 Ruby 生態系統中的其他工具強制型別。Matz 曾表示 Ruby 不會在內部成為一種型別化語言,因此在需要時依賴外部工具來強制型別化。
也可以看看
- rbs
- typeprof
- 陡
- 院子
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/495326.html
