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

如何用Sorbet为DelegateClass的子类添加类型注解?

为DelegateClass继承的类添加Sorbet类型注解的解决方案

问题背景

需要为一个继承自DelegateClass(SomeClass)的Ruby类添加Sorbet类型注解。Kernel#DelegateClass接收一个类作为参数,返回新类,该类会将方法委托给传入类的实例。

示例代码如下:

class ExtArray < DelegateClass(Array)
  def find_by_some_complex_condition
    find { |element| some_complex_condition(element) }
  end
end

直接使用上述写法会触发Sorbet错误:不允许父类为非常量。尝试用别名定义委托类:

DelegatedArray = DelegateClass(Array)

class ExtArray < DelegatedArray
  def find_by_some_complex_condition
    find { |element| some_complex_condition(element) }
  end
end

同样无法通过检查,因为Sorbet不允许父类为类别名。需要找到符合Sorbet要求的写法,允许移除DelegateClass但必须保留委托功能。

环境信息:

  • Ruby 3.0.5
  • Sorbet 0.5.10751
  • Tapioca 0.11.4
  • macOS Ventura 13.3.1

可行解决方案

方法1:手动实现方法委托

放弃DelegateClass,手动将需要的方法委托给内部实例,同时添加完整的类型注解:

# typed: true
class ExtArray
  extend T::Sig

  sig { params(array: Array[T.untyped]).void }
  def initialize(array)
    @array = array
  end

  # 委托find方法
  sig { params(block: T.proc.params(element: T.untyped).returns(T::Boolean)).returns(T.untyped) }
  def find(&block)
    @array.find(&block)
  end

  sig { returns(T.untyped) }
  def find_by_some_complex_condition
    find { |element| some_complex_condition(element) }
  end

  # 按需添加更多委托方法,比如length
  sig { returns(Integer) }
  def length
    @array.length
  end
end

def some_complex_condition(element)
  rand < 0.5
end

这种方式类型清晰,Sorbet能完全识别,但需要手动维护所有需要委托的方法。

方法2:使用Sorbet内置的T::Delegator

Sorbet提供T::Delegator模块,可实现类型安全的自动委托,替代Ruby标准库的DelegateClass:

# typed: true
class ExtArray
  extend T::Sig
  include T::Delegator

  sig { params(delegate: Array[T.untyped]).void }
  def initialize(delegate)
    @delegate = delegate
  end

  sig { returns(T.untyped) }
  def find_by_some_complex_condition
    find { |element| some_complex_condition(element) }
  end
end

def some_complex_condition(element)
  rand < 0.5
end

T::Delegator会自动将未定义的方法委托给@delegate实例,Sorbet能正确识别这些委托方法的类型。只需确保初始化方法中设置@delegate变量即可。

方法3:添加类型存根文件

若要保留DelegateClass的写法,可通过Tapioca生成或手动创建类型存根。比如创建sig/delegate.rbi文件:

class DelegatedArray < Array
end

之后在代码中使用:

# typed: true
DelegatedArray = DelegateClass(Array)

class ExtArray < DelegatedArray
  extend T::Sig

  sig { returns(T.untyped) }
  def find_by_some_complex_condition
    find { |element| some_complex_condition(element) }
  end
end

这种方式需要维护存根文件,适合需保留原有代码结构的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 19:02:44