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

标签 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::Null 继承自 KeyGetter
  3. 提取一个“接口(interface)”并期待它。

    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 类型的方法。这样做时不要迷失在语法中)。

那么,是的,我们可以在此处内联注释 duck 类型吗?如果不是,我们应该怎么做?

最佳答案

But what I'd like to know (and didn't find in the docs) is: can I describe the duck type? Like we can do in YARD, for example:

我发现 sorbet 对具有特定键的散列的支持非常有限(流称为 "sealed object" )。您可以尝试这样的操作,但 foo 将被识别为 T::Hash[T.untyped, T.untyped],或者至多为 T::哈希[字符串,字符串]

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]),但这与您自己定义类/接口(interface)没有什么不同。

Sorbet 确实支持元组,但在这里也不理想。 See on Sorbet.run

Or is it an inherently flawed idea (because ideally we need to annotate the duck type's methods too. And not get lost in syntax while doing that).

既然要注解duck类型的方法,那就更需要为它定义一个类了。在您概述的方法中,我最喜欢选项 (2)。

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

KeyGetter::NULL = KeyGetter.new(nil)

关于ruby - 我可以在方法签名中指定鸭子类型吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56703466/

相关文章:

ruby - 将 Sorbet RBI 文件添加到 gems

ruby-on-rails - Sorbet 找不到 `has_many` 关联方法

ruby - 将字符串转换为ruby中的变量名

sorbet - 忽略冰糕中的一行

ruby-on-rails - 如何更改此 ruby​​ 匹配线以返回 true 或 false

PHP 相当于 Ruby 符号

ruby - 你会如何用冰糕做玫瑰内存?

c# - 什么是动态语言,为什么 C# 不符合条件?

ruby-on-rails - 将 URL 参数添加到 link_to 路径