Skip to content

sorbet: avoid Dynamic type, refine examples.rb#35

Open
bennn wants to merge 1 commit intoutahplt:mainfrom
bennn:sorbet-dyn
Open

sorbet: avoid Dynamic type, refine examples.rb#35
bennn wants to merge 1 commit intoutahplt:mainfrom
bennn:sorbet-dyn

Conversation

@bennn
Copy link
Copy Markdown
Member

@bennn bennn commented Mar 12, 2026

  • Don't use T.untyped (the dynamic type) because it makes type narrowing trivial. No need for is_a? checks or anything when the value is untyped.
  • Change filter to promise an array of integers. This makes Sorbet fail, which is too bad seems more fair.

rainfall fails unexpectedly now. I'll ask for help in a comment below.

New benchmark results:


$ racket _benchmark.rkt   
(Benchmark         Sorbet)
(positive          O     )
(negative          O     )
(connectives       O     )
(nesting_body      O     )
(struct_fields     x     )
(tuple_elements    x     )
(tuple_length      x     )
(alias             O     )
(nesting_condition O     )
(merge_with_union  O     )
(predicate_2way    x     )
(predicate_1way    x     )
(predicate_checked x     )

$ racket _benchmark.rkt -e
(Benchmark Sorbet)
(filter    x     )
(flatten   O     )
(tree_node x     )
(rainfall  x     )

@bennn bennn marked this pull request as ready for review March 12, 2026 22:27
@bennn
Copy link
Copy Markdown
Member Author

bennn commented Mar 12, 2026

@robertDurst I need some Ruby help.

Q1

How would you write a function that takes anything and checks if the value is an integer? Here's pseudocode (from README.md in this repo):

# positive
define f(x: Top) -> Top:
    if x is String:
        return String.length(x) // type of x is refined to String
    else:
        return x

Right now in Sorbet, we have this code using Object:

# typed: strict
extend T::Sig

sig { params(x: T.anything).returns(Integer) }
def positive_success_f(x)
  if x.is_a?(String)
    x.length
  else
    0
  end
end

I'd rather use the Sorbet top type T.anything but it seems too hard to work with. We'd have to do a case statement. By chance, do you know a better way?
https://sorbet.org/docs/anything

The error message that I get from running Sorbet makes me think there's something easier, but when I try replacing x.is_a? with x.class.is_a? (which is what I think the error is telling me!), I get another error message:

$ bundle exec srb tc foo.rb     
foo.rb:6: Method is_a? does not exist on T.anything https://srb.help/7003
     6 |  if x.is_a?(String)
               ^^^^^
  Got T.anything originating from:
    foo.rb:5:
     5 |def positive_success_f(x)
                               ^
  There is a singleton class method with the same name:
    https://github.com/sorbet/sorbet/tree/06f78f051811eea111f223a5487c733b29a2f890/rbi/core/kernel.rbi#L543: Defined here
     543 |  def is_a?(arg0); end
            ^^^^^^^^^^^^^^^
    Either:
    - use .class to call it, or
    - remove self. from its definition to make it an instance method
  Autocorrect: Use -a to autocorrect
    foo.rb:6: Insert .class
     6 |  if x.is_a?(String)
              ^
Errors: 1

Q2

Can you figure out a way to use is_a? to turn an Object into a Hash? Here's what I tried:

# typed: strict
extend T::Sig

## Example rainfall
## success
sig { params(weather_reports: T::Array[Object]).returns(Float) }
def rainfall_success(weather_reports)
  total = T.let(0.0, Float)
  count = T.let(0, Integer)
  weather_reports.each do |day|
    if day.is_a?(T::Hash[Symbol, Object]) && !day.nil?
      if day.key?(:rainfall)
        val = T.let(day[:rainfall], Object)
        if val.is_a?(Float) && 0.0 <= val && val <= 999.0
          total += val
          count += 1
        end
      end
    end
  end
  count > 0 ? total / count : 0.0
end

The error message suggests that I'm way off base ... but if I change all the Object to T.untyped then the code typechecks so maybe it's not totally crazy to put T::Hash[...] inside an is_a? check? I'm so confused.

foo.rb:11: Expected T::Module[T.anything] but found Runtime object representing type: T::Hash[Symbol, Object] for argument arg0 https://srb.help/7002
    11 |    if day.is_a?(T::Hash[Symbol, Object]) && !day.nil?
                         ^^^^^^^^^^^^^^^^^^^^^^^
  Expected T::Module[T.anything] for argument arg0 of method Kernel#is_a?:
    https://github.com/sorbet/sorbet/tree/06f78f051811eea111f223a5487c733b29a2f890/rbi/core/kernel.rbi#L539:
     539 |      arg0: T::Module[T.anything],
                ^^^^
  Got Runtime object representing type: T::Hash[Symbol, Object] originating from:
    foo.rb:11:
    11 |    if day.is_a?(T::Hash[Symbol, Object]) && !day.nil?
                         ^^^^^^^^^^^^^^^^^^^^^^^
  Detailed explanation:
    It looks like you're using Sorbet type syntax in a runtime value position.
    If you really mean to use types as values, use T::Utils.coerce to hide the type syntax from the type checker.
    Otherwise, you're likely using the type system in a way it wasn't meant to be used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant