**选项的正确RBS签名是什么

  • 本文关键字:是什么 RBS 选项 ruby rbs
  • 更新时间 :
  • 英文 :


以下Ruby方法的正确RBS描述是什么:

def insert( id:, **options )
# ...
'Hello World'
end

假设以下用法:

insert( id: 123, a: 'A', b: 'B' )

TL;DR

RBS语法并不总是有助于告诉您签名在给定任意调用时可能接受什么。它只是告诉签名实际定义了什么,以及可能返回的方法。因此,试图根据使用签名的方式而不是实际的签名定义来定义RBS结构可能不会产生预期的结果。

使用RBS或TypeProf生成签名

RBS

由于您发布的#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非常聪明,它清楚地告诉您,您既没有定义:id关键字的类型,也没有定义**options关键字参数将接受的值的名称或类型。这是因为没有将它们定义为签名本身的一部分。

然而,它也知道该方法返回一个具有已知值的String,并且告诉您是这样的

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使用所提供的how示例来推断:id和收集的关键字参数预期采用的预期类型。在您发布的示例中,:id取一个Integer,您收集的关键字参数都是String对象,并且该方法仍然返回一个String,尽管TypeProf不像rbs prototype那样假定该String的内容

如果你以多种方式调用你的方法,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 prototypetypeprof现在都返回大致相同的结果。您的签名也被更清楚地记录下来,因为您明确地定义了期望的关键字参数,而不是将其留给未知关键字参数的零散集合。

目前,RBS语法并不打算取代像@param这样的YARD标记。如果您想要不强制执行的文档级别,请使用YARD。如果你想强制执行签名契约或非鸭子类型,那么你需要使用RBS语法来验证你的代码,并使用Steep、Sorbet或Ruby生态系统中的其他工具强制执行类型。Matz表示,Ruby在内部不会成为一种类型化语言,因此它依赖外部工具在需要时强制执行类型化。

另请参阅

  • rbs
  • 打字教授
  • 陡峭的
  • 堆场

最新更新