Sorbet Cheatsheet Part 2
Published on
Note: This was an incomplete draft. You probably want the finished version!
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.
# 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.
# 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) }
end
# Define parameter value types with `params`.
sig { params(n: Integer).returns(String) }
(1..n).map { greet }.join()
end
# Define keyword parameters the same way.
sig { params(n: Integer, sep: String).returns(String) }
(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
end
# For a method whose return value is useless, use `void`.
sig { params(name: String).void }
puts
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 }
if kwargs[:op] ==
args.each { puts(i - 1) }
else
args.each { puts(i + 1) }
end
end
# Most initializers should be `void`.
sig { params(name: String).void }
# 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.
= T.let(, String)
# Class variables too.
@@the_answer = T.let(42, Integer)
end
# TODO: T::Boolean, T::Array, T::Range, T::Hash, T::Enumerable, ...
# TODO: T.nilable
# TODO: T.class_of
# TODO: T.proc
end
# TODO: T.any
# TODO: T.all
end
# TODO: T::Struct
# TODO: ^ reference equality, not value equality
# TODO: T::Enum
end
# TODO: T.absurd
# TODO: T.noreturn
end
# TODO: T.type_alias
# TODO: T.self_type
# TODO: T.attached_class
end
# TODO: abstract!
# TODO: interface!
# TODO: abstract. / override.
# TODO: mixes_in_class_methods
# TODO: final!
# TODO: sealed!
end
# TODO: T.reveal_type
end
# 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.
# TODO: T.enum
end
# TODO: type_parameters / T.type_parameter
# TODO: T::Generic
# TODO: type_member
end