Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would you do rose memoization with Sorbet?

Tags:

ruby

sorbet

Trying to annotate this code, the rose memoization (@||=) gives me an error Use of undeclared variable @git_sha.

# typed: strict
# frozen_string_literal: true

module Util
  extend T::Sig

  sig { returns(String) }
  def self.git_sha
    @git_sha ||= ENV.fetch(
      'GIT_REV',
      `git rev-parse --verify HEAD 2>&1`
    ).chomp
  end
end

As far as I've found, I should declare the variable's type with T.let but haven't figured out specifically how.

like image 310
mikker Avatar asked Mar 04 '23 19:03

mikker


1 Answers

Sorbet now has built-in support for this, as of 0.4.4679. Before that, there were other workarounds (see below).

  1. Initialize the instance variable as T.nilable, and replace all direct access of the instance variable elsewhere with the method:
# typed: strict
# frozen_string_literal: true

module Util
  extend T::Sig

  sig { returns(String) }
  def self.git_sha
    @git_sha = T.let(@git_sha, T.nilable(String))
    @git_sha ||= ENV.fetch(
      'GIT_REV',
      `git rev-parse --verify HEAD 2>&1`
    ).chomp
  end
end

→ View on sorbet.run

This is the the preferred solution.

  1. Initialize the instance variable outside of the method, and give it a type annotation:
# typed: strict
# frozen_string_literal: true

module Util
  extend T::Sig

  @git_sha = T.let(nil, T.nilable(String))

  sig { returns(String) }
  def self.git_sha
    @git_sha ||= ENV.fetch(
      'GIT_REV',
      `git rev-parse --verify HEAD 2>&1`
    ).chomp
  end
end

→ View on sorbet.run

Conceptually, there are two phases of execution for this class: when it's initialized, and when it's used. If an instance variable is not given a type annotation when it is initialized in Sorbet, it will be T.untyped everywhere (or an error in # typed: strict). Because if it's not annotated in the initialize, Sorbet can't know which code path might write into this location first. (Even in this case where there is one location, Sorbet doesn't do that sort of global analysis.)

Sorbet only relaxes this when the instance variable is nilable, in which case it can be initialized anywhere, because Sorbet doesn't need to guarantee that it's initialized as non-nil.

  1. Use a different strictness level.

Docs on strictness levels.

If you find it too burdensome to add a type annotation, you can opt out of requiring a type annotation by using # typed: true, where the error requiring type annotations for instance variables is silenced.

like image 105
jez Avatar answered May 13 '23 02:05

jez