Values and Operations

Dear Computer

Chapter 3: Types

Values and Operations

In English, we can pick any noun and mix it with any verb to form a sentence. For example: Blue melts, Trees vote, and Fish shatter. The sentences may not make sense, but they are grammatically correct. Programming language grammars similarly allow us to mix data and operations in ways that don't make sense, as in these Ruby expressions:

Ruby
puts "abcdefghijklmnopqrtuvwxyz" / 7
puts 62.length

Changing the grammar to prevent the formation of nonsense is too difficult. Instead, we add a notion of types to our data and our operations. A type describes two things:

After the compiler or interpreter parses a program, it walks the tree representation and ensures that the values and operations are legal and compatible. Any errors that arise at this point are not syntax errors, but type errors.

Programming without types is like eating without taste buds. If you were to somehow turn off your taste buds, you could eat anything, including spoiled or poisonous food, and you wouldn't know the food was bad until it was already inside your system. Your taste buds send early signals to your brain about the safety of your food. So does a language's typechecker send signals to the compiler or interpreter that data and operations are being used incorrectly. A momentary bad taste is unpleasant, but it is better than a trip to the hospital. Likewise, errors in code are frustrating, but they are better than failures at runtime.

The set of values for a type might be fully and explicitly enumerated by the programmer, as in an enum. This C++ enum defines a new type that consists of only three values:

C++
enum Category {
  Animal,
  Vegetable,
  Mineral
};

For other types, the set of values is a property of the language or the computer. A boolean admits only true and false values. An unsigned byte admits values from 0 to 255. A string admits a sequence of zero or more Unicode characters.

The operations that a type supports may be builtin. For example, many languages provide arithmetic, relational, and bitwise operations for integers. In other cases, the developer is the one deciding which operations are supported. This is especially true in object-oriented programming. When you define a new class, you are effectively creating a brand new type. Each method you define is an operation supported by that type. This Ruby program defines a brand new type for points in 2D space with operations for shifting them and converting them to a string representation:

Ruby
class Point2
  def initialize(x, y)
    @x = x
    @y = y
  end

  def shift(dx, dy)
    @x += dx
    @y += dy
  end

  def to_s
    "(#{@x}, #{@y})"
  end
end

point = Point2.new(-1, 0)
point.shift(2, 3)
puts point.to_s

In this chapter, we explore the many different ways that types influence how we write and run code. By the end, you'll be able to answer the following questions:

The answers to these questions vary widely across our programming languages.

Primitive Types →