不明白如何编写和设置 ruby 3 RBS 类型检查。
您在这里混淆了三个完全不同的事情:
- 类型语言
- 类型系统
- 类型检查器
RBS 是#1,一种类型语言。这正是它听起来的样子:一种用于写下Types的语言。
它没有说明这些类型的含义(这将是一个Type System),它也没有说明一个程序是否相对于一个类型系统是良好类型的(这将是一个Type Checker)。
RBS 是基于观察到社区中已经存在多种类型检查器而开发的。例如,几乎每个 Ruby IDE 都包含一个类型检查器,以便能够提供更好的代码完成和“红色波浪线”。有冰糕。有陡峭。还有其他几个。
Ruby 社区中已经存在多种不兼容的类型语言。Ruby 核心库和标准库 RDoc 注释有时使用一种非正式的注释。YARD 有类型注释,但没有类型语言。(他们只给出了类型语言的示例。)有些人使用示例类型语言编写 YARD 类型注释,因此请使用自制或修改过的。然后是 Sorbet 使用的 RBI。
RBS 背后的想法是提供一种类型语言作为 Ruby 语言的一部分,并提供一个用于查询该语言的 API。这将一方面允许现有类型检查器之间的互操作性,另一方面允许更多竞争。(目前,有一种锁定效应:每个类型检查器都使用不同的类型语言,因此如果不使用不同的类型语言从头开始重写类型签名,则无法轻松交换它们。如果它们使用通用类型语言,您可以轻松地将它们换掉并为您的用例选择最好的。)
RBS 项目的后半部分是为整个 Ruby 核心库和标准库提供一套完整的 RBS 签名。这是当前事务状态的另一个问题:每个类型检查器都必须一遍又一遍地这样做,他们都必须从头开始用自己的类型语言为 Ruby 核心和标准库编写类型签名。使用 RBS,将为 Ruby 核心和标准库提供一组标准的类型签名。
此外,为 Rails 之类的项目编写签名变得更加容易,因为您只需编写一组签名,并且您知道它将适用于每个类型检查器、每个 IDE、每个 linter、每个静态分析器等。
问题:.rbs
在我的文件示例中,文件应该是什么样子?
这取决于:你想说什么?
class Book[T]
@pages: T
def initialize: (pages: T) -> void
def pages: () -> T
end
是一种可能的类型定义。
class Book[T]
attr_reader pages: T
def initialize: (pages: T) -> void
end
可能比第一个更好地捕捉您的意图。(因为我不知道你的意图,但只能看到你的代码,我不知道哪个更好。)
class Book
attr_reader pages: 100
def initialize: (pages: 100) -> void
end
是另一种可能的类型定义。这是仍然允许您的示例代码运行的最严格的类型签名。
class Book
attr_reader pages: Numeric
def initialize: (pages: Numeric) -> void
end
也是可以的,原样
class Book
attr_reader pages: Integer
def initialize: (pages: Integer) -> void
end
当然,这也总是有效的,尽管没有用,因为它没有告诉我们任何我们还不知道的事情:
class Book
attr_reader pages: untyped
def initialize: (pages: untyped) -> untyped
end
从技术上讲,您代码中的两种方法都采用可选块,而我们在这里不允许这样做。为了完全匹配代码的语义,签名可能应该是这样的:
class Book[T]
@pages: T
def initialize: (pages: T) ?{ (*args: void) -> void } -> void
def pages: () ?{ (*args: void) -> void } -> T
end
最后一个是与您的代码当前所做和允许的最匹配的一个。这是否也准确地捕获了您希望代码执行和允许的操作,这是一个完全不同的问题,只有您可以回答。
我应该运行什么 CLI 命令来测试它?
目前尚不清楚“测试它”是什么意思。如果您的意思是“类型检查”,那么正如我之前所说:RBS不是类型检查器,它只是一种用于编写类型的语言,它不知道也不关心您对这些类型做了什么。
然而, RBS确实有一个测试模式,但它的作用有所不同:它可以查看您正在运行的代码,并查看您的代码在运行时是否违反了任何类型约束。但是,这不是静态类型检查。
例如,如果您有此签名:
class Foo
def bar: () -> Integer
def quux: () -> Integer
end
这个代码:
class Foo
def bar; 23 end
def quux; 'fourty-two' end
end
foo = Foo.new
foo.bar
它不会抱怨,因为它从未观察到Foo#quux
在运行时违反类型约束。
如果您有以下定义Foo#quux
:
def quux; if rand < 0.5 then 42 else 'fourty-two' end end
然后它有时会抱怨,有时不会,只要你真的打电话quux
到某个地方。
因此,您是否发现任何问题取决于您的测试覆盖率。如果您有一个方法在使用特定的参数组合调用时返回错误的类型,但您在测试中从未使用这种特定的参数组合调用它,那么您将永远不会注意到。
请注意,这与其说是测试代码的正确性,不如说是测试类型签名的正确性。
例如$rbs my_project *
或类似的东西?
RBS 附带的工具主要设计为库,因为预计它们将被集成到文档工具(以在文档中显示类型签名)、测试工具(以测试您的类型签名)、IDE(用于代码完成) ),或在类型检查器中。
例如,我上面提到的测试工具打算这样使用:
RBS_TEST_TARGET='Foo::*' bundle exec ruby -r rbs/test/setup test/foo_test.rb
什么rbs/test/setup
会做,非常愚蠢地只是要求Foo.constants.grep(Module)
,然后对于每个模块mod.instance_methods(false)
,用一个包装器替换每个方法,该包装器检查方法入口和出口的类型并调用原始方法。(使用与 ActiveSupport 相同的技术alias_method_chain
。)
如您所见,这对命令行不太友好,它旨在作为库集成到测试工具中。
有几个命令行工具可用:
rbs ast
以 JSON 格式打印出当前环境的 AST
rbs list
打印出当前环境中的类型列表
rbs ancestors
打印出模块的祖先
rbs methods
打印出模块的方法
rbs method
打印出一个方法
rbs validate
验证 RBS 文件的语法并进行一些基本的语义完整性检查
rbs constant
执行持续查找
rbs paths
打印出 RBS 查找签名文件的路径
rbs prototype
可以生成骨架类型签名以帮助您入门,它可以从您的代码或已经存在的 RBI 类型签名执行此操作。(RBI 是 Sorbet 类型检查器使用的类型语言。)
rbs vendor
供应商签名文件到项目目录
rbs parse
解析 RBS 文件并打印语法错误
rbs test
使用上述注入的测试钩子运行您的测试
但是,请注意,此 CLI并非旨在作为 RBS 的主要接口。自述文件明确指出:
gem 附带rbs
命令行工具以演示它可以做什么并帮助开发 RBS。
CLI 旨在作为如何使用 API的示例,而不是作为 API 的主要入口点。