What Kind of Thing?

When a child learns language, they learn to categorize. "Dog" is not just a sound---it's a category that includes poodles, terriers, and wolves, but excludes cats, tables, and clouds.

Programming has the same need. When we write functions, we need to know: what kind of thing can this function receive? What kind of thing does it return? Can I add these two values? Can I ask this value for its "name"?

These questions are answered by types---the categories of the programming world.

Types as Categories

A type is a category of values that share common characteristics. Values of the same type can be used in the same ways.

Consider some basic types:

Each type defines what operations make sense:

Number
quantities like 42, 3.14, -7, 0
Text
sequences of characters like "hello", "chess", "g1"
Boolean
truth values: true or false
python
    // Numbers can be added
    3 + 5              // makes sense: 8
    
    // Text can be concatenated
    "hello" + "world"  // makes sense: "helloworld"
    
    // But mixing them is nonsense
    3 + "hello"        // what would this even mean?

Types prevent nonsense. They tell us what questions we can ask and what operations we can perform.

The Type of Our Chess State

Let's think about the type of our chess game state. What kind of thing is it?

yaml
    ChessState:
        board: Board
        turn: Color
        
    Board:
        64 squares, each containing: Piece OR empty
        
    Piece:
        color: Color
        kind: PieceKind
        
    Color:
        white OR black
        
    PieceKind:
        king OR queen OR rook OR bishop OR knight OR pawn

This is a type description. It tells us:

  • A ChessState contains a Board and a Color
  • A Board contains 64 squares
  • Each square is either empty or contains a Piece
  • A Piece has a color and a kind
  • And so on...

Why Types Matter

Types serve as a contract---a promise about what kind of value you'll receive and what kind you must provide.

Consider our apply_move function:

    apply_move: (ChessState, Move) → ChessState

This type signature tells us:

If we accidentally pass the wrong kind of thing---a number where we needed a chess state---we'd like to catch that mistake early. And we can! Tools can inspect the program's definition, checking that every function receives the kind of thing it expects, without even running the program. These tools are called type checkers.

  1. We must give it a valid chess state (not a number, not text)
  2. We must give it a valid move
  3. We will get back a chess state
python
    apply_move(42, "hello")    // Error: 42 is not a ChessState
    apply_move(state, 3.14)    // Error: 3.14 is not a Move

Types catch errors before the program runs. They're a form of automated checking---a safety net woven from categories.

Type Checking: Static vs Dynamic

Before a program can run, it must be translated from human-readable code into instructions the computer understands. This translation is called compilation, and the tool that does it is a compiler---think of it as a meticulous translator who reads your manuscript before it goes to print.

Once translated, the program runs---it executes, comes alive, does its work. This is runtime: the program in motion, responding to inputs, producing outputs.

Languages differ in when they check types:

Static typing: Types are checked before the program runs. If there's a type error, the program won't even start. Languages like Haskell, TypeScript, and Rust work this way.

Dynamic typing: Types are checked while the program runs. Errors appear only when problematic code is actually executed. Languages like Python, JavaScript, and Ruby work this way.

python
    // In a statically typed language:
    function double(x: Number): Number
        return x * 2
    
    double("hello")  // Error at compile time!
                     // Program won't start.
python
    // In a dynamically typed language:
    function double(x):
        return x * 2
    
    double("hello")  // Error at runtime!
                     // Crashes when this line runs.

Both approaches use types. The question is when the checking happens.

Types as Documentation

Beyond error prevention, types serve as documentation. They tell readers (including your future self) what kind of data flows through the program.

Compare:

python
    // Without types - what does this return?
    function get_result(input):
        ...
    
    // With types - ah, it takes a Player and returns a Score!
    function get_result(input: Player): Score
        ...

The typed version is self-documenting. We don't need to read the implementation to understand what goes in and what comes out.

Inventing New Types

We're not limited to built-in types like Number and Text. We can define our own types---categories specific to our domain.

For chess, we invented:

  • ChessState
  • Board
  • Piece
  • Color
  • PieceKind
  • Move

Each type is a vocabulary word. Together, they form a language for talking about chess.

typescript
    function is_checkmate(state: ChessState): Boolean
    function get_legal_moves(state: ChessState): List of Move
    function apply_move(state: ChessState, move: Move): ChessState

These signatures tell a story. Even without seeing the implementations, we understand the shape of the system.

We've seen that types are categories---ways of classifying values so we know what operations make sense. But so far, our types have been simple: a thing is a Number, or a Piece, or a State.

In the next chapter, we explore how to build complex types from simple ones---combining types with AND and OR.