0

I have code that looks like this (playground link):

# typed: strict
class A
  extend T::Sig

  sig { returns(T::Array[Integer]) }
  def compute_expensive
    [1, 2, 3]
  end
  
  sig { returns(T::Array[Integer]) }
  def expensive
    @expensive ||= T.let(compute_expensive, T::Array[Integer])
  end
end

This fails to typecheck, saying that:

editor.rb:12: The instance variable @expensive must be declared inside initialize or declared nilable https://srb.help/5005
    12 |    @expensive ||= T.let(compute_expensive, Integer)
            ^^^^^^^^^^

I've tried a couple things to get around this…

  • When I declare the type as T.nilable(Integer), Sorbet says that the return type does not match the sig. Fair.
  • When I declare the type in initialize as @expensive = nil, Sorbet says that nil does not type check with the Integer definition below. Also fair.
  • If I declare @expensive = [] in initialize, my assignment with ||= becomes unreachable.
  • I can of course say @expensive = compute_expensive if @expensive.empty? and then return @expensive but I'm more interested in how Sorbet's type system can accommodate the ||= pattern.

This feels like a really common pattern in Ruby to me! How can I get Sorbet to type-check it for me?

Brian Hicks
  • 6,213
  • 8
  • 51
  • 77
  • 1
    https://stackoverflow.com/questions/56693361/how-would-you-do-rose-memoization-with-sorbet – jez Oct 07 '20 at 18:12
  • Does this answer your question? [How would you do rose memoization with Sorbet?](https://stackoverflow.com/questions/56693361/how-would-you-do-rose-memoization-with-sorbet) – FullOnFlatWhite Oct 07 '20 at 18:32

1 Answers1

2

A Playground Link right back to you.

So, really using the initialize is the important part here.

  sig { void }
  def initialize
    @expensive = T.let(nil, T.nilable(T::Array[Integer]))
  end

Because the memoization is still nil up until the point it's actually called, you have to allow for it to be nil, along with T::Array[Integer], which then neccessitates for you to add it to the initialization to make the class sound.

FullOnFlatWhite
  • 3,642
  • 2
  • 18
  • 23
  • 1
    FWIW, you don't necessarily need the `initialize`, you can do it inline too [Playground Link](https://sorbet.run/#%23%20typed%3A%20strict%0Aclass%20A%0A%20%20extend%20T%3A%3ASig%0A%0A%20%20sig%20%7B%20returns(T%3A%3AArray%5BInteger%5D)%20%7D%0A%20%20def%20compute_expensive%0A%20%20%20%20%5B1%2C%202%2C%203%5D%0A%20%20end%0A%20%20%0A%20%20sig%20%7B%20returns(T%3A%3AArray%5BInteger%5D)%20%7D%0A%20%20def%20expensive%0A%20%20%20%20%40expensive%20%7C%7C%3D%20T.let(nil%2C%20T.nilable(T%3A%3AArray%5BInteger%5D))%0A%20%20%20%20%40expensive%20%7C%7C%3D%20compute_expensive%0A%20%20end%0Aend) – Rob Di Marco Oct 07 '20 at 14:38