Composite types.

We have learned about primitive types — the basic building blocks. But real programs need to represent complex things. A person is not just a name or just an age. A person has a name AND an age AND an address AND more.

Today we learn how to combine simple types into complex structures. This is composition. Building big from small.

Primitive types are powerful but limited.

An integer can represent an age. A string can represent a name. But what represents a person?

In the real world, things have multiple attributes. A person has a name, age, address, email. A product has a name, price, quantity. An order has items, total, shipping address.

To model these, we need to combine multiple values into a single unit. We need composite types.

A composite type groups multiple values together. It says: these values belong together. They represent one thing.

This is fundamental to organizing data. Without composition, we would have disconnected primitives everywhere. With composition, we have meaningful structures that mirror the real world.

The most common composite type is the record, also called a struct or product type.

A record bundles named fields together. Each field has a name and a type.

A Person record might have: name of type String, age of type Int, email of type String.

All three fields belong to the same Person. When you have a Person, you have all three values.

Records are like forms. The form defines what fields exist. Each filled form is an instance with specific values.

Why named fields? Because names give meaning. Instead of "the first string" and "the second string", you have "name" and "email". The structure documents itself.

Records are everywhere. User profiles. Configuration settings. API responses. Any time you have multiple related pieces of data, records are often the answer.

To use a record, you first define its structure: what fields it has and their types.

Then you create instances by providing values for each field.

Finally, you access fields by name.

Define: Person has name (String), age (Int). Create: alice equals Person with name "Alice", age 30. Access: alice dot name gives "Alice", alice dot age gives 30.

The record type ensures all instances have the same structure. Every Person has a name and age. You cannot create a Person without them.

This consistency is valuable. When you receive a Person, you know exactly what fields are available. No surprises.

Tuples are another way to combine values. Unlike records, tuples use position rather than names.

A tuple is an ordered collection of values. The first value, the second value, and so on.

A pair of coordinates might be: (3, 7). The first element is x, the second is y. Position determines meaning.

Tuples are simpler than records. You do not need to define a new type. Just group values together.

But tuples are less self-documenting. What does (3, 7) mean? Coordinates? A range? An ID and count? Without names, you must rely on context.

Tuples are good for quick groupings. Returning multiple values from a function. Temporary pairings. When the meaning is obvious from context.

Records are better for important domain concepts. When you will use the structure in many places. When clarity matters.

Records combine values with AND. A Person has a name AND an age AND an email.

But sometimes we need OR. A payment is made by credit card OR bank transfer OR cash. Not all three. Exactly one.

This is where choice types come in. They are also called sum types, tagged unions, or variants.

A choice type says: a value is one of several possibilities. Each possibility may have different data.

PaymentMethod is one of: CreditCard with number and expiry, BankTransfer with accountNumber, or Cash with no extra data.

A PaymentMethod value is exactly one of these. If it is CreditCard, it has card data. If it is Cash, it has no extra data.

Choice types model alternatives. Status codes. Error types. Different kinds of messages. Any time something can be "this or that".

When you have a choice type, you must handle each possibility.

You cannot just access fields like with a record. First, you must determine which variant you have.

This is pattern matching or case analysis. Check which variant, then access its data.

When payment is CreditCard, use payment dot number. When payment is BankTransfer, use payment dot accountNumber. When payment is Cash, there is no data to access.

The compiler can ensure you handle all cases. If you forget one, it warns you. This makes choice types safe.

This exhaustive handling is a feature. It forces you to think about all possibilities. When you add a new variant, the compiler tells you everywhere that needs updating.

Composite types can contain other composite types. This is nesting.

An Order might contain: customer of type Customer (another record), items as a List of OrderItem (records), shippingAddress of type Address (another record).

Customer, OrderItem, and Address are themselves composite types with their own fields.

This nesting allows modeling complex domains. A small set of primitive types becomes a rich vocabulary of structures.

The key is composability. Simple types combine into records. Records combine into larger structures. Each level adds meaning while staying manageable.

Good data design creates clear hierarchies. Each level is understandable on its own. The whole emerges from composing parts.

When do you use each?

Records: when you have multiple named attributes that belong together. The standard choice for domain objects. Person, Product, Order.

Tuples: when you need a quick grouping without defining a type. Returning coordinates. Temporary pairings. When meaning is clear from context.

Choice types: when a value can be one of several alternatives. Error handling. Status codes. Any "is-a" relationship where one type is a specific case of another.

Often you combine them. A record might have a field that is a choice type. A choice type's variants might contain records.

They are complementary tools. Use the one that best expresses your data's structure.

There is another way to build complex types: inheritance. A Student is a Person with additional fields.

Inheritance creates an "is-a" relationship. A Student is a Person.

Composition creates a "has-a" relationship. An Enrollment has a Student.

Both have their place. But composition is often more flexible.

With inheritance, the relationship is fixed. A Student will always be a Person.

With composition, you can change parts independently. An Enrollment can have any Student. The Student can be replaced.

Modern practice favors composition. Build complex things by combining simpler things. This keeps each piece simple and reusable.

Let us summarize.

Composite types combine multiple values into a single unit.

Records bundle named fields together. They are the standard way to represent domain objects.

Tuples bundle values by position. They are good for quick groupings when names are unnecessary.

Choice types represent alternatives. A value is one of several possibilities.

Composite types can be nested. Records contain records. Structures build on structures.

Choose the right composite for your data. Records for "has-these-attributes". Choice types for "is-one-of-these".

Composition is powerful. It turns simple building blocks into rich data models.