Monkey Patching
The operations that a type supports seem set in stone by the developer of the type. On integers, we can perform arithmetic, bitwise, and relational operations. On booleans, we can perform negative, conjunction, and disjunction operations. On strings we can search, replace, extract, append, and change capitalization. If we want any operation beyond what's provided, we're out of luck, right? In many languages, yes. But some languages are more mutable than others. Some allow us to add new operations to existing types.
In Ruby, integers are instances of the builtin Integer
class, which doesn't normally support subscripting via []
. But classes in Ruby are open, which means you can add new methods to them. Modifying classes and other structures that were defined elsewhere is called monkey patching. This program monkey patches the Integer
class to add a subscript operation that pulls out a single decimal digit:
class Integer
# Provide place-wise access to digits.
def [](index)
# Divide to shift digit into ones place.
# Mod to get just the ones place.
self / (10 ** index) % 10
end
end
n = 62
puts n[0] # prints 2
puts n[1] # prints 6
A subscript of 0 pulls out the ones-place digit, 1 the tens-place digit, and so on. Being able to add new operations to existing types is perhaps dangerous but also amazing.
Summary
Type systems are the nerves of a program. They communicate bad news, but they are not its cause. They sense when a value or operation is being used incorrectly and cause the compiler or interpreter to reject your program. A language's type system decides a program's fitness by consulting its catalog of types. A type is the set of legal values and the operations that may be applied to them. Some types are baked into a language, but programmers may also define new ones or add new operations to existing types. As a result, the catalog of types is expansive. The primitive types describe simple data like numbers and characters. Composite types build on other types. Scalar types describe a singular value, and non-scalar types describe a collection of values. Values may be transformed from one type to another via explicit casting or implicit coercion. Programmers may be required to formally annotate the types of variables and parameters, but an ever-growing number of languages are capable of automatically inferring types from the source code. If the compiler detects and checks types when building an executable, its type system is static. If the interpreter checks types at runtime, its type system is dynamic. If the interpreter examines the legality of individual operations rather than types, its uses duck typing. The static or dynamic nature of a type system is significant: it influences the speed of a program, the verbosity and reusability of its code, and the process of discovering errors.