我可以在方法签名中指定鸭子类型吗?

时间:2019-06-21 12:26:38

标签: ruby sorbet

下面是示例代码:

# typed: true

class KeyGetter

  sig {params(env_var_name: String).returns(KeyGetter)}
  def self.from_env_var(env_var_name)
    return Null.new if env_var_name.nil?

    return new(env_var_name)
  end

  def initialize(env_var_name)
    @env_var_name = env_var_name
  end

  def to_key
    "key from #{@env_var_name}"
  end

  def to_s
    "str from #{@env_var_name}"
  end

  class Null
    def to_key; end
    def to_s; end
  end
end

在其上运行srb tc失败,并

key_getter.rb:7: Returning value that does not conform to method result type https://srb.help/7005
     7 |    return Null.new if env_var_name.nil?
            ^^^^^^^^^^^^^^^
  Expected KeyGetter
    key_getter.rb:6: Method from_env_var has return type KeyGetter
     6 |  def self.from_env_var(env_var_name)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Got KeyGetter::Null originating from:
    key_getter.rb:7:
     7 |    return Null.new if env_var_name.nil?
                   ^^^^^^^^

我看到几种解决方法:

  1. 在签名中使用类似.returns(T.any(KeyGetter, KeyGetter::Null))的字样。
  2. 使KeyGetter::NullKeyGetter继承。
  3. 提取一个“接口”并期望它。

    class KeyGetter
      module Interface
        def to_key; end
        def to_s; end
      end
    
      class Null
        include KeyGetter::Interface
      end
    
      include Interface
    
      sig {params(env_var_name: String).returns(KeyGetter::Interface)}
      def self.from_env_var(env_var_name)
        return Null.new if env_var_name.nil?
    
        return new(env_var_name)
      end
    

但是我想知道的(并且在文档中找不到)是:我可以描述鸭子的类型吗?就像我们在YARD中所做的一样,例如:

 # @returns [(#to_s, #to_key)]

或者这是一个天生有缺陷的想法(因为理想情况下,我们也需要注释一下duck类型的方法。这样做时不要在语法上迷失方向。)

是的,我们可以在这里内嵌鸭子类型吗?如果没有,我们应该怎么做?

1 个答案:

答案 0 :(得分:2)

  

但是我想知道的(并且在文档中找不到)是:我可以描述鸭子的类型吗?就像我们在YARD中所做的一样,例如:

我发现sorbet对具有特定键(称为流程"sealed object"的哈希)的支持非常有限。您可以尝试类似的方法,但是foo将被识别为T::Hash[T.untyped, T.untyped],或者最多是T::Hash[String, String]

extend T::Sig

sig { returns({to_s: String, to_key: String}) }
def foo
  T.unsafe(nil)
end

T.reveal_type(foo)
foo.to_s
foo.to_key

See on Sorbet.run

他们尝试使用Typed Struct[T::Struct])解决此问题,但这与您自己定义类/接口没有什么不同。

Sorbet确实支持元组,但这也不是理想的选择。 See on Sorbet.run

  

或者这是一个天生有缺陷的想法(因为理想情况下,我们也需要注释一下duck类型的方法。这样做时不要在语法上迷失方向。)

鉴于您要注释鸭子类型的方法,因此为它定义一个类就变得更加重要。在您概述的方法中,我最喜欢选项(2)。

您也可以改为使NULL为常量。但是考虑到当前代码的实现方式,它可能不如选项(2)

KeyGetter::NULL = KeyGetter.new(nil)