Getting Started with EK9 · Chapter 13

Two Ways to Build: Data, Functions and Objects

So far you have built programs from the types EK9 provides — String, Money, List and the rest — joined together with functions. From here on you make your own types, named in the words of the problem you are solving: a Point, a Score, a Player. Before the details, this chapter sets out the choice you face when you do, because there is more than one good way to build, and EK9 lets you use them together.

Two traditions

Programming has two long-standing ways of organising a program.

The first keeps data and behaviour apart. You describe your data as a plain bundle of named fields — a point is an x and a y — and write separate functions that take that data and do things with it. The data is passive; the functions do the work. This is the tradition of the C language and its struct, and it is simple and direct.

The second bundles data and behaviour together. A Counter is not just a number; it is a number and the operations that belong to it — increment, current — packaged as one thing, an object, that looks after itself. This is the tradition of Smalltalk, and later C++, Java and many others.

Both have built large, successful systems. Neither is "right". They suit different problems, and often different parts of the same problem.

EK9 does not make you choose

Many languages pick a side. C has structs and functions but no objects. Java made almost everything an object and, for a long time, had no free-standing functions at all. In either, you shape your problem to fit the language.

In EK9, data, behaviour and objects are equal partners, and you pick whichever fits. There are three building blocks, and you have already glimpsed each of them.

A record is data — a bundle of named fields:

    Point
      x as Float: 0.0
      y as Float: 0.0
      default operator ?

A function is behaviour on its own. It takes something in, hands something back, and holds no state, as in the Functions chapter:

    add() as pure
      ->
        a as Integer
        b as Integer
      <- rtn as Integer: a + b

A class is an object: data and the behaviour that belongs with it:

    Counter
      count as Integer: 0

      increment()
        count += 1

      current() as pure
        <- rtn <- Integer(count)

      default operator ?

current hands back Integer(count) — a copy of the count, not the field itself.1

None of the three is second-class. A record is not a lesser class, and a function does not have to live inside an object. Each is a complete way to build, and one program can use all three.

Which should you reach for?

A rough guide, which the next chapters make precise:

You do not have to decide perfectly. A program can mix all three, and you can change your mind later.

Protecting data without freezing it

If a record's fields can be read and written by any code, what stops a distant piece of code from changing one when it should not?

Many languages answer by freezing the data: an "immutable" record that can never be changed. EK9 does not. It controls the code instead. A function or method marked pure — the word from the Functions chapter — may not change anything it is handed. Pass a record through pure code and it comes back exactly as it went in.

So the record stays plain and open, and the protection sits on the behaviour, not on the data. A value that is frozen in one part of a program and changeable in another brings complications of its own; putting the rule on the code avoids them. pure gets its full treatment in the second volume.

Note. record and class are both kinds of aggregate — a construct that defines a type with members of its own: fields, methods, operators. (A function is not an aggregate; of the three building blocks, only record and class are.) Aggregates share a few rules (every aggregate that holds fields defines a ?, so it can answer "am I set?"), which the next chapter introduces. Later you will meet three more aggregates: the trait, for behaviour shared across types (Traits and Composition), the component, for putting a whole subsystem behind a simple interface (Components), and, in the second volume, the service, for web services. The Constructs at a Glance appendix summarises them all on one page.

Try it yourself

  1. Write a pure function distanceFromOrigin that takes a Point and returns how far it is from (0, 0). (sqrt written in front of a number gives its square root.) For the point (3.0, 4.0) it should give 5.0.
  2. Give Counter a reset() method that puts the count back to 0, and print the count before and after.
  3. For each of these, decide whether a record, a function or a class fits best, and say why in one line: a name and an email address; a count of page visits; turning Celsius into Fahrenheit.

Recap

There are three ways to build: data, behaviour and objects. Two traditions lie behind them — keeping data and functions apart, and bundling them into objects — and EK9 treats record, function and class as equal partners you can mix freely. A record's data stays open; pure is how EK9 protects it. Next: records and enumerations — how to declare your own types, give them fields, and give them a little behaviour.

  1. Handing the field back directly would give the caller a reference to the counter's own state, which they could then use to change it. Returning a copy, Integer(count), keeps the counter in charge of its value. The Classes chapter explains this in full; the examples adopt the habit from the start. ↩