🌱 Beginner's Guide lesson 9 of 12

Classes with tnak

In the last lesson, structs were always copied, like photocopies. That's great for small values like points and money. But some things in a program aren't meant to be copied: there's only one player in a game, one bank account, one scoreboard, and every part of the program should see the same one. For those, steav2 has classes.

What is a tnak?

tnak means class. Like a sampoan, a class is a type you design, with fields and methods. The big difference is how you build one, and what happens when you share it.

[ try-it.sts ]
bongSlanhOun "counter";
tnak Counter {
    akthe count: LekKut;

    rupamun init() {
        nis.count = 0;
    }

    rupamun tick() {
        nis.count += 1;
    }
}

akthe clicks = Counter();
clicks.tick();
clicks.tick();
clicks.tick();
jongyeytha clicks.count;
[ what it prints ]
3

Two new things here:

  • rupamun init is a special method that runs automatically when you build a new object with Counter(). It initializes (sets up) the fields. A class must set its fields itself in init, instead of listing them in brackets like a struct.
  • nis means this: "the object this method belongs to". nis.count means "this counter's count". (You can also just write count, like in struct methods, but nis. makes it very clear.)

The value you build from a class is called an object or an instance.

Unlike a struct's method, a class's method can change its fields: tick() really does add one to the count.

Giving init some inputs

init can take parameters, just like any function. Whatever you put in the brackets when building the object gets passed to init:

[ try-it.sts ]
bongSlanhOun "player";
tnak Player {
    akthe name: Ahsor;
    akthe health: LekKut;

    rupamun init(name: Ahsor) {
        nis.name = name;
        nis.health = 100;
    }

    rupamun hurt(damage: LekKut) {
        nis.health -= damage;
        ber (nis.health <= 0) {
            jongyeytha nis.name + " is knocked out!";
        }
    }
}

akthe hero = Player("Neary");
hero.hurt(30);
jongyeytha hero.name + " has " + hero.health + " health";
hero.hurt(80);
[ what it prints ]
Neary has 70 health
Neary is knocked out!

Notice nis.name = name;: the parameter is called name and so is the field. nis.name is the field, and plain name is the parameter. That's a handy reason to use nis..

Shared, not copied

Here's the big difference from sampoan. When you put an object into another variable, you don't get a copy. Both variables point at the same object, like two people holding the same balloon by two strings:

[ try-it.sts ]
bongSlanhOun "shared";
tnak Scoreboard {
    akthe points: LekKut;

    rupamun init() {
        nis.points = 0;
    }
}

akthe mine = Scoreboard();
akthe yours = mine;
yours.points += 50;
jongyeytha mine.points;
jongyeytha yours.points;
[ what it prints ]
50
50

We only changed yours, but mine changed too, because there's only one scoreboard. Compare that with the Point copy in the last lesson.

The same goes for functions. Hand an object to a function, and the function works on the very same object, so its changes stick:

[ try-it.sts ]
bongSlanhOun "bank";
tnak Account {
    akthe balance: LekKut;

    rupamun init(start: LekKut) {
        nis.balance = start;
    }
}

rupamun payPocketMoney(account: Account) {
    account.balance += 5;
}

akthe savings = Account(20);
payPocketMoney(savings);
payPocketMoney(savings);
jongyeytha "Savings: " + savings.balance;
[ what it prints ]
Savings: 30

Printing an object directly just tells you what kind of object it is:

[ try-it.sts ]
bongSlanhOun "print";
tnak Robot {
    rupamun init() {}
}
jongyeytha Robot();
[ what it prints ]
<Robot instance>

Arrays are shared too

Surprise: you've been using a shared type all along! Arrays behave like classes, not structs. Two variables holding "the same" array really hold one array:

[ try-it.sts ]
bongSlanhOun "shared arrays";
akthe list = ["eggs"];
akthe sameList = list;
sameList.push("milk");
jongyeytha list;
[ what it prints ]
[eggs, milk]

sampoan or tnak?

sampoan (struct) tnak (class)
building Point(1, 2), fields in order Player("Neary"), runs init
assigning or passing makes a copy shares the same one
methods can change fields no yes
good for small values: points, colours, money things with one identity: players, accounts, scenes

A good rule of thumb: if two things with the same values are really the same thing (two Point(1, 2)s are equally good), use sampoan. If it's something with its own identity that changes over time (your bank account isn't the same as mine, even with the same balance), use tnak.

Your turn

  1. Make a tnak Lamp with a Boolean field on, starting ort, and a method flip() that switches it. Hint: nis.on = !nis.on;
  2. Give the Player class a heal(amount: LekKut) method that never lets health go above 100.
  3. Make two variables that share one Lamp, flip it using one variable, and print on using the other.

Solution for exercise 1:

[ try-it.sts ]
bongSlanhOun "lamp";
tnak Lamp {
    akthe on: Boolean;

    rupamun init() {
        nis.on = ort;
    }

    rupamun flip() {
        nis.on = !nis.on;
    }
}

akthe lamp = Lamp();
lamp.flip();
jongyeytha "on: " + lamp.on;
lamp.flip();
jongyeytha "on: " + lamp.on;
[ what it prints ]
on: ok
on: ort
  • steav2♡ help
  • Y2K✓ ready
  • wasminside
  • .stsvalid

best viewed at 1024x768 ☆ made with ♡ ☆ the reference is steav2's own docs