You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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)

关键说明

  1. 泛型参数定义:在抽象Service类中定义InitParams和CallReturn两个泛型参数,分别对应子类初始化器的参数类型和实例call方法的返回值类型。
  2. self.call签名:通过params(args: InitParams)和returns(CallReturn)明确约束类方法的参数与返回值,与子类的实现绑定。
  3. 子类泛型指定:每个子类通过type_member指定自己的泛型参数具体类型,比如ServiceA的InitParams定义为[T.untyped, {a: String}],表示无位置参数,仅接受关键字参数a且类型为String。
  4. 类型转换:使用T.cast让Sorbet识别子类实例的类型,确保调用call方法时的类型正确性。

内容的提问来源于stack exchange,提问作者Amiratak88

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.08 09:50:42