如何为Ruby中Sorbet抽象父类的透传方法定义方法签名?
使用Sorbet为抽象Service类的self.call定义类型签名
问题场景
我们计划采用Sorbet,需要为一种常用的Service模式添加类型签名:存在抽象Service类,其中的self.call类方法负责初始化子类实例并调用实例的call方法。核心需求是:
self.call的参数必须匹配子类初始化器的参数self.call的返回值必须匹配子类实例call方法的返回值
原代码示例
# typed: true class Service extend T::Sig extend T::Helpers abstract! def self.call(...) new(...).call end sig{abstract.void} def call end end class ServiceA < Service extend T::Sig extend T::Helpers sig{params(a: String).void} def initialize(a:) @a = a end sig{override.void} def call puts @a end end ServiceA.call(a: 'some value')
当前报错(已翻译)
editor.rb:10: 仅在数组大小可静态确定的场景下支持使用Splats语法
解决方案:利用Sorbet泛型实现类型约束
通过T::Generic定义泛型参数,明确约束子类的初始化参数和返回值类型,即可让Sorbet正确检查self.call的参数与返回值。修正后的代码如下:
# typed: true class Service extend T::Sig extend T::Helpers extend T::Generic abstract! # 定义泛型参数:InitParams对应初始化参数,CallReturn对应实例call方法的返回值 InitParams = type_member(fixed: T.untyped) CallReturn = type_member(fixed: T.untyped) sig { params(args: InitParams).returns(CallReturn) } def self.call(*args, **kwargs) # 通过T.cast让Sorbet识别子类实例类型 instance = T.cast(new(*args, **kwargs), Service) instance.call end sig { abstract.returns(CallReturn) } def call; end end class ServiceA < Service extend T::Sig extend T::Helpers # 指定当前子类的泛型参数:无位置参数,关键字参数a为String;call方法返回值为void type_member(:InitParams, fixed: [T.untyped, {a: String}]) type_member(:CallReturn, fixed: T.void) sig { params(a: String).void } def initialize(a:) @a = a end sig { override.returns(T.void) } def call puts @a end end # Sorbet会正确校验参数:必须传入a: String ServiceA.call(a: 'some value') # 以下代码会被Sorbet报错(参数类型不匹配) # ServiceA.call(a: 123)
关键说明
- 泛型参数定义:在抽象
Service类中定义InitParams和CallReturn两个泛型参数,分别对应子类初始化器的参数类型和实例call方法的返回值类型。 - self.call签名:通过
params(args: InitParams)和returns(CallReturn)明确约束类方法的参数与返回值,与子类的实现绑定。 - 子类泛型指定:每个子类通过
type_member指定自己的泛型参数具体类型,比如ServiceA的InitParams定义为[T.untyped, {a: String}],表示无位置参数,仅接受关键字参数a且类型为String。 - 类型转换:使用
T.cast让Sorbet识别子类实例的类型,确保调用call方法时的类型正确性。
内容的提问来源于stack exchange,提问作者Amiratak88
相关产品推荐
相关产品推荐

