Reference Types - cyxigo/wi GitHub Wiki

Introduction

By now we know (almost) every single value type Wi has to offer, but they are represented differently in the Wi's interpreter! I won't go much into detail how Wi represents values, but one thing you need to know – some of them are represented as "boxes": values that need to be allocated and managed by Wi's garbage collector, and these values are passed by reference in memory: strings, arrays, maps, foreigns, functions, objects, userdata, and modules. That means when you write:

a := [];
b := a;

They are now referencing the same box in memory. So when you write:

b->add(1);

Not only b becomes [1], but so does a: they are the same box.

Importance of knowing this

This is important because when you use objects and clone them you need to be careful about reference types. Just writing:

obj_a := object {
    x: [];
};

a := new obj_a;

Is a bad idea since a will have the same exact array as obj_a, let's even check:

obj_a := object {
    x: [];
};

a := new obj_a;

std::puts(obj_a.x); %% <array 0x55caf51ae8b0>
std::puts(a.x);     %% <array 0x55caf51ae8b0> ...Huh?!

a.x->add(1); %% <--- BAD!!! obj_a.x is now [1] too!!

So YOU better write:

obj_a := object {
    x: [];
};

a := new obj_a {
    x: [];
};

a.x->add(1); %% Amazing! obj_a is untouched!

Or even BETTER...!

Constructor functions

Re-assigning reference typed fields in every new works yes, but you will hate it once your object has more than um 1 reference typed field, so a good pattern is defining a constructor function, which does everything for you:

obj_a := object {
    x: [];
};

new_a := || => new obj_a {
    x: [];
};

a1 := new_a();
a2 := new_a();
a3 := new_a();

a1.x->add(1); %% Amazing! obj_a is untouched!
a2.x->add(2); %% Amazing!! obj_a is untouched!
a3.x->add(3); %% Amazing!!! obj_a is untouched!

This is a pretty simple example but of course you can do much more with constructor functions! For example, adding some behavior or parameters to set fields as:

obj_a := object {
    x: 0;
};

new_a := |x| => {
    std::puts("obj_a created with x: ${x}!");
    return new obj_a {
        x: x;
    };
};

a1 := new_a(1); %% obj_a created with x: 1!
a2 := new_a(2); %% obj_a created with x: 2!
a3 := new_a(3); %% obj_a created with x: 3!

std::puts(a1.x); %% 1
std::puts(a2.x); %% 2
std::puts(a3.x); %% 3

[!TIP] You should almost always use constructor functions. They are much safer and flexible. If your object later gets a field that is a reference type you can just modify constructor function to include the new behavior, avoiding TONS of code update.

You can not worry about strings since they're immutable and stuff, but you need to be careful passing arrays, maps, and objects around. You probably expect referencing with objects but not with arrays and maps. Lucky for you, Wi's standard method library offers us copy methods for arrays and maps:

array1 := [1, 2, 3];
array2 := array1->copy();

std::puts(array1); %% <array 0x5642ae2a8940>
std::puts(array2); %% <array 0x5642ae2a89a0> <--- Different!

map1 := { "x": 10 };
map2 := map1->copy();

std::puts(map1); %% <map 0x5600da265e50>
std::puts(map2); %% <map 0x5600da265e90> <--- Different!

Why?

Because new (and array.copy/map.copy) are what we call shallow in programming. They do create new boxes and copy their data, but their data can contain reference types too! And that data can contain data too, and that data... and so on! An opposite of that would be deep copy – Wi doesn't provide you with that and it's up to you whether you need it or not.

Equality

It's very very worth mentioning that when you write:

a == b;

And both of them are reference types – Wi compares them by reference, not by what they actually are. Lucky for us, Wi's standard library comes to rescue again with the std::equals function!

a := [1, 2, 3];
b := [1, 2, 3];

std::puts(a == b);            %% false
std::puts(std::equals(a, b)); %% true

You may notice though that strings are an exception. That's because Wi does something called string interning – strings with the same contents are also the same boxes in memory.

Next: Modules