Here's example code:
# 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
Running srb tc
on it fails with
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?
^^^^^^^^
I see several ways of working around this:
.returns(T.any(KeyGetter, KeyGetter::Null))
in the sig.KeyGetter::Null
inherit from KeyGetter
.Extract an "interface" and expect that.
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
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:
# @returns [(#to_s, #to_key)]
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).
So yes, can we annotate the duck type inline here? If not, what should we do instead?
Java does not support duck typing. The method signature has to specify the correct type for each argument. For example, an argument has to implement the “duck” interface. It is not enough for a class to have the same methods that are provided for in the interface; no, it is necessary to actually write implements Duck.
What languages support duck typing? Python and Ruby support duck typing, both of which are dynamic typed languages. In general, dynamic typed languages such as JavaScript and TypeScript support duck typing.
Duck Typing is a type system used in dynamic languages. For example, Python, Perl, Ruby, PHP, Javascript, etc.
The term Duck Typing is a lie.
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:
I've found sorbet has very limited support for hash with specific keys (what flow calls "sealed object"). You could try something like this, but foo
would be recognized as T::Hash[T.untyped, T.untyped]
, or at most 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
They attempt to resolve that with Typed Struct ([T::Struct]
), but that'd be no different from you defining the class/interface yourself.
Sorbet does support tuple but that wouldn't be ideal here either. 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).
Given that you want to annotate the duck type's methods, it's all the more to define a class for it. I like the option (2) the best among the approaches you outlined.
You can also make NULL a constant value instead. But given how the current code is implemented, it's probably not as good as option (2)
KeyGetter::NULL = KeyGetter.new(nil)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With