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…
T.nilable(Integer)
, Sorbet says that the return type does not match the sig. Fair.initialize
as @expensive = nil
, Sorbet says that nil
does not type check with the Integer
definition below. Also fair.@expensive = []
in initialize
, my assignment with ||=
becomes unreachable.@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?
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.