如何在 Sorbet 中为具有属性的散列定义签名?

How to define a signature for a hash with attributes in Sorbet?

(请注意,这在 sorbet.run 上不可重现,据我所知,它只能通过 Sorbet 的本地副本重现)

我希望我可以使用 Typed Structs feature 创建一个方法签名,其中一个参数是 options 哈希,但这不起作用:

# typed: true
require 'sorbet-runtime'
extend T::Sig

class OptionsStruct < T::Struct
  prop :x, Integer, default: 1
end

sig { params(options: OptionsStruct).void }
def method(options)
  puts options.x
end

# This works
method(OptionsStruct.new({x: 2}))

# This causes the typechecker to throw.
method({x: 2})

本质上,当您对该文件进行类型检查时,它会抱怨传递了一个散列,而这正是预期的结构。我的问题是:如何为具有特定参数的散列定义有效签名?结构显然在这里不起作用。虽然我没有尝试过 Shapes,但根据文档,它们非常有限,所以如果可能的话我宁愿不要使用它们。

documentation on generics 提到了散列,但似乎暗示它们只能在散列的键和值都是相同类型的情况下使用(例如 Hash<Symbol, String> 要求所有键都是符号,所有值是字符串),并且没有提供任何方式(据我所知)来定义具有特定键的散列。

谢谢!

基本上,您必须选择多种方式之一(您已经提到的三种方式):

  1. 使用 T::Hash[KeyType, ValueType]。这允许您在调用将其作为参数的方法时使用 {} 语法,但强制您为每个条目使用相同类型的键和值。
  2. 使用 T::Hash[KeyType, Object]。这在值的类型上更灵活一些......但是你丢失了类型信息。
  3. 使用 T::Hash[KeyType, T.any(Type1, Type2, ...)。这是 1 和 2 之间的中间地带。
  4. 使用形状。正如文档所说,功能可能会改变并且是实验性的。这是在不向调用者强加使用 T::Struct 的情况下模拟这样的东西的最好方法:
sig { params(options: {x: Integer}).void }
def method(options)
  puts options[:x]
end
  1. 像您一样使用 T::Struct。这会强制您使用 MyStruct.new(prop1: x, prop2: y, ...)
  2. 调用该方法

它们都是有效的,其中 4 和 5 是类型安全性最高的。在这两者中,4 对调用者来说是最灵活的,但是 5 是你知道的 Sorbet 不会改变 short/medium 术语中的支持。