Got a question that the wiki doesn't answer? Ask on the forum (preferred), or join us on IRC.

BeastNode

CommandHelper/Staged/Understanding References

From EngineHub.org Wiki
Jump to: navigation, search


In mscript, all values are passed by value of a reference, and assignments are too. However, primitives do not have a "reference," so are simply passed by value of. It is important to understand what this means in terms of how this will affect your code. Since this is a difficult concept to understand even for seasoned programmers, the concept will be described through examples.

Reference vs. Value

In programming in general, when you create a variable, it can be created in two ways, it can either create a new object/value, or it can simply point to an existing one. When you have a pointer to an existing value, it works much like a symlink on a file system; making changes to a pointer actually makes changes to the value pointed to. However, assignments (and by extension passing a value to a procedure) change what value the variable points to. For primitives, that is, a string or a number, this concept doesn't actually matter, because you can't "change" the underlying primitive value, you can only reassign a new value to the existing variable, and that assignment doesn't affect other variables.

This code demonstrates assignment by value of primitives.

1      @a = 1;
2      @b = @a# Note we are assigning @b to @a
3      msg(@a); # Msg's 1
4      msg(@b); # Also msg's 1
5      @a = 5; # Now we are assigning something different to @a
6      msg(@a); # Msg's 5
7      msg(@b); # Msg's 1 still


As you can see, even though @b is assigned to @a, since it is actually assigned by value, changing the value of @a does not affect @b. This mechanism works in all cases for arrays as well.

1      @a = array(1, 2, 3);
2      @b = @a;
3      msg(array_implode(@a', ')); # Msg's 1, 2, 3
4      msg(array_implode(@b', ')); # Msg's 1, 2, 3
5      @a = array(4, 5, 6);
6      msg(array_implode(@a', ')); # Msg's 4, 5, 6
7      msg(array_implode(@b', ')); # Still msg's 1, 2, 3


When we do the second assignment to @a, we aren't changing the value of what's being pointed to, we are simply changing what we are pointing to.

Now, let's look at changing the underlying value of an array.

1      @a = array(1, 2, 3);
2      @b = @a;
3      msg(array_implode(@a', ')); # Msg's 1, 2, 3
4      msg(array_implode(@b', ')); # Msg's 1, 2, 3
5      array_set(@a, 0, 4);
6      array_set(@a, 1, 5);
7      array_set(@a, 2, 6);
8      msg(array_implode(@a', ')); # Msg's 4, 5, 6
9      msg(array_implode(@b', ')); # Now msg's 4, 5, 6


As you can see, we are now changing the underlying value, instead of completely changing what value as a whole @a references. What might be confusing is that assignments provide a convenience method for setting values in arrays, so this code performs the same way:

1      @a = array(1, 2, 3);
2      @b = @a;
3      msg(array_implode(@a', ')); # Msg's 1, 2, 3
4      msg(array_implode(@b', ')); # Msg's 1, 2, 3
5      @a[0] = 4;
6      @a[1] = 5;
7      @a[2] = 6;
8      msg(array_implode(@a', ')); # Msg's 4, 5, 6
9      msg(array_implode(@b', ')); # Now msg's 4, 5, 6


Keep in mind that it's not really an assign, however, it's simply an array_set() wrapped up in a more aesthetic syntax.

When you think of sending parameters to a procedure as assign()'s, it becomes easier to see how the same concept applies to a proc as well.

01   
02   proc(_myProc, @a@b){
03      @a = array('a''b''c');    # This call might actually get optimized out
04                                 # entirely since it's not used, but
05                                 # anyway, it has no effect
06   
07      @b[0] = 4;
08      @b[1] = 5;
09      @b[2] = 6;
10   }
11   
12   @myA = array(1, 2, 3);
13   @myB = @myA;
14   
15   msg(array_implode(@myA', ')); # msg's 1, 2, 3
16   msg(array_implode(@myB', ')); # msg's 1, 2, 3
17   _myProc(@myA@myB);
18   msg(array_implode(@myA', ')); # msg's 4, 5, 6
19   msg(array_implode(@myB', ')); # msg's 4, 5, 6


As you can see here, though @a was re-assigned in the procedure, it did not affect @myA, as you can essentially think of the procedure call as working by doing several mini-assignments: assign(@a, @myA) assign(@b, @myB). When we change the assignment of @a inside the procedure, @myA is not affected, however changing the internals of the value @b, that does affect @myB (and by extension @myA, since @myA points to @myB).

If we seemingly "inline" (we actually aren't inlining it, see below) the procedure, we get different results though:

01   @myA = array(1, 2, 3);
02   @myB = @myA;
03   
04   msg(array_implode(@myA', ')); # msg's 1, 2, 3
05   msg(array_implode(@myB', ')); # msg's 1, 2, 3
06   # _myProc:
07   @myA = array('a''b''c');
08   @myB[0] = 4;
09   @myB[1] = 5;
10   @myB[2] = 6;
11   
12   msg(array_implode(@myA', ')); # msg's a, b, c <- Note the difference here
13   msg(array_implode(@myB', ')); # msg's 4, 5, 6


This works differently, because we have forgotten that a procedure call introduces an extra "assignment" before it runs. To truly "inline" a procedure, we must do an assignment beforehand, so something like this:

01   @myA = array(1, 2, 3);
02   @myB = @myA;
03   
04   msg(array_implode(@myA', ')); # msg's 1, 2, 3
05   msg(array_implode(@myB', ')); # msg's 1, 2, 3
06   
07   # This bit duplicates the implied assignments done by the procedure
08   @a = @myA;
09   @b = @myB;
10   
11   #_myProc:
12   @a = array('a''b''c');
13   @b[0] = 4;
14   @b[1] = 5;
15   @b[2] = 6;
16   
17   
18   msg(array_implode(@myA', ')); # msg's 4, 5, 6 <- Now it's back to what it was with an actual procedure
19   msg(array_implode(@myB', ')); # msg's 4, 5, 6


Why the differences?

An array could be quite large, so copying it around each time it is assigned could severely impact performance and memory usage. So by default, it copies the reference instead of the value (which is constant sized and much smaller typically), however, the easy clone operator means that you can quickly copy the value, without much hassle, if that's truly what you need.

1   # Copy by reference
2   @array1 = array(1, 2, 3);
3   @array2 = @array1;
4   @array1[0] = 'a';
5   msg(@array1); # msg's a, 2, 3
6   msg(@array2); # msg's a, 2, 3


The above example copied by reference, as was explained in the earlier section. Changes to @array1 are reflected in @array2, because they both "point" to the same array. The variable itself only contains a reference to the array, not the value of the array itself. Contrast this to a cloned array, which copies the value over:

1   #Copy by value
2   @array1 = array(1, 2, 3);
3   @array2 = @array1[]; #Note the clone operation here
4   @array1[0] = 'a';
5   msg(@array1); # msg's a, 2, 3
6   msg(@array2); # msg's 1, 2, 3


In this example, the value wasn't changed in array2. However, this comes at a cost. For large arrays, we now have two mostly identical copies of the array in memory. Sometimes this is required, and if so, there's nothing that can be done. But since most often this isn't strictly required, there's no point in eating up more memory than we actually need. Why aren't primitives handled the same way then? Well, actually, strings are reference based, thanks to the way the JVM works, but since they aren't modifiable, assigning by reference or value doesn't make a difference. But for other primitives, passing by value is done because there is no memory hit for reference vs value, because in general, the memory used to "point" to an int will be the same as just keeping the value of the int itself, but the extra layer of abstraction would slow down accesses. So passing by value makes more sense for primitives.



Navigation menu