Sorbet Cheatsheet Part 2

Published Nov 14, 2020

Time to spend another 15 minutes collecting some Sorbet examples! The earlier draft can be found in another Thinkin' Log.

There are no new examples in this one, but the to-dos are more complete and better-organized. I think this includes all the types that I’ve actually used in my beginner-to-intermediate experience.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# Every file should have a "typed sigil" that tells Sorbet how strict to be
# during static type checking.
#
# Strictness levels (lax to strict):
#
# ignore: Sorbet won't even read the file.  This means its contents are not
# visible during type checking.  Avoid this.
#
# false: Sorbet will only report errors related to constant resolution.  This
# is the default if no sigil is included.
#
# true: Sorbet will report all static type errors.  This is the sweet spot of
# safety for effort.
#
# strict: Sorbet will require that all methods, constants, and instance
# variables have static types.
#
# strong: Sorbet will no longer allow anything to be T.untyped, even
# explicitly.  Almost nothing satisfies this.

# typed: true

# Include the runtime type-checking library.  This lets you write inline sigs
# and have them checked at runtime (instead of running Sorbet as RBI-only).
# These runtime checks happen even for files with `ignore` or `false` sigils.
require 'sorbet-runtime'

class BasicSigs
  # Bring in the type definition helpers.  You'll almost always need this.
  extend T::Sig

  # Sigs are defined with `sig` and a block.  Define the return value type with
  # `returns`.
  #
  # This method returns a value whose class is `String`.  These are the most
  # common types, and Sorbet calls them "class types".
  sig { returns(String) }
  def greet
    'Hello, World!'
  end

  # Define parameter value types with `params`.
  sig { params(n: Integer).returns(String) }
  def greet_repeat(n)
    (1..n).map { greet }.join("\n")
  end

  # Define keyword parameters the same way.
  sig { params(n: Integer, sep: String).returns(String) }
  def greet_repeat(n, sep: "\n")
    (1..n).map { greet }.join(sep)
  end

  # Notice that positional/keyword and required/optional make no difference
  # here.  They're all defined the same way in `params`.

  # For lots of parameters, it's nicer to use do..end and a multiline block
  # instead of curly braces.
  sig do
    params(
      str: String,
      num: Integer,
      sym: Symbol,
    ).returns(String)
  end
  def uhh(str:, num:, sym:)
    'What would you even do with these?'
  end

  # For a method whose return value is useless, use `void`.
  sig { params(name: String).void }
  def say_hello(name)
    puts "Hello, #{name}!"
  end

  # Splats! Also known as "rest parameters", "*args", "**kwargs", and others.
  #
  # Type the value that a _member_ of `args` or `kwargs` will have, not `args`
  # or `kwargs` itself.
  sig { params( args: Integer, kwargs: String).void }
  def no_op(*args, **kwargs)
    if kwargs[:op] == 'minus'
      args.each { |i| puts(i - 1) }
    else
      args.each { |i| puts(i + 1) }
    end
  end

  # Most initializers should be `void`.
  sig { params(name: String).void }
  def initialize(name:)
    # Instance variables must have annotated types to participate in static
    # type checking.

    # The value in `T.let` is checked statically and at runtime.
    @upname = T.let(name.upcase, String)

    # Sorbet can infer this one!
    @name = name
  end

  # Constants also need annotated types.
  SORBET = T.let('A delicious frozen treat', String)

  # Class variables too.
  @@the_answer = T.let(42, Integer)
end

class StandardHelpers
  # TODO: T::Boolean, T::Array, T::Range, T::Hash, T::Enumerable, ...
  # TODO: T.nilable
  # TODO: T.class_of
  # TODO: T.proc
end

class Combinators
  # TODO: T.any
  # TODO: T.all
end

class DataClasses
  # TODO: T::Struct
  # TODO: ^ reference equality, not value equality
  # TODO: T::Enum
end

class FlowSensitivity
  # TODO: T.absurd
  # TODO: T.noreturn
end

class Metaprogramming
  # TODO: T.type_alias
  # TODO: T.self_type
  # TODO: T.attached_class
end

class InheritanceChecks
  # TODO: abstract!
  # TODO: interface!
  # TODO: abstract. / override.
  # TODO: mixes_in_class_methods
  # TODO: final!
  # TODO: sealed!
end

class Debugging
  # TODO: T.reveal_type
end

class EscapeHatches
  # TODO: T.untyped
  # TODO: T.cast
  # TODO: T.unsafe
  # TODO: T.must
  # TODO: T.assert_type!
end

# The following types are not officially documented but are still useful.

class ValueSet
  # TODO: T.enum
end

class Generics
  # TODO: type_parameters / T.type_parameter
  # TODO: T::Generic
  # TODO: type_member
end