Howto:Start using vectors and hashes in Nasal: Difference between revisions

Jump to navigation Jump to search
m
Many correct improvements, but unfortunately also some breaking the correctness of the article (set size vs setsize, away vs. a way) - please review, thanks - Undo revision 125681 by Chillz (talk)
(Fixing 50 grammatical errors)
m (Many correct improvements, but unfortunately also some breaking the correctness of the article (set size vs setsize, away vs. a way) - please review, thanks - Undo revision 125681 by Chillz (talk))
Line 52: Line 52:
</syntaxhighlight>
</syntaxhighlight>


Now, this is fairly repetitive and not overly scalable, because the variable names are hardcoded and need to be changed in a lot of places in the source code.  
Now, this is fairly repetitive and not overly scalable, because the variable names are hard coded and need to be changed in a lot of places in the source code.  


Just imagine we'd need to support more than just 5 waypoints, like maybe 10, 20, 50 or maybe even 1000 waypoints. As you can see, this method is very inflexible, complicated, tedious and error-prone.
Just imagine we'd need to support more than just 5 waypoints, like maybe 10, 20, 50 or maybe even 1000 waypoints. As you can see, this method is very inflexible, complicated, tedious and error-prone.
Line 117: Line 117:
</syntaxhighlight>
</syntaxhighlight>


Whenever you see such a code, it should be obvious that using arrays/vectors would be a good idea.
Whenever you see such code, it should be obvious that using arrays/vectors would be a good idea.


So, it would be better to use a vector of waypoints instead. A vector is a list of things (variables) that can be easily accessed using an index into the vector. Pretty much like an array in C or C++, with the added advantage that the vector can be dynamically resized, e.g. using the setsize() library call. Consider the following example:
So, it would be better to use a vector of waypoints instead. A vector is a list of things (variables) that can be easily accessed using an index into the vector. Pretty much like an array in C or C++, with the added advantage that the vector can be dynamically resized, e.g. using the setsize() library call. Consider the following example:
Line 168: Line 168:
</syntaxhighlight>
</syntaxhighlight>


What we need to save all the other waypoint specific information is a new variable type that serves as the "container" for variables, so that we can save several variables for each waypoint.
What we really need to save all the other waypoint specific information is a new variable type that serves as the "container" for variables, so that we can save several variables for each waypoint.


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
Line 182: Line 182:
== A vector based version ==
== A vector based version ==


One simple way to accomplish this is by using another vector for each waypoint, nested inside the original vector. So that we end up with a two-dimensional data structure. For example, imagine a folder containing subfolders, with folders not having names but rather indices (numbers).
One simple way to accomplish this is using a another vector for each waypoint, nested inside the original vector. So that we end up with a two-dimensional data structure. For example, imagine a folder containing sub folders, with folders not having names but rather indices (numbers).


So we could be using a "main" vector to store all waypoints, while each field of the main vector is a different vector that contains all waypoint-specific data.
So we could be using a "main" vector to store all waypoints, while each field of the main vector is a different vector that contains all waypoint-specific data.
Line 199: Line 199:
</syntaxhighlight>
</syntaxhighlight>


First of all, we are setting up all the different variables for wp4, next, you are storing all variables in a vector called "waypoint4". In the end, we store this vector in another vector named "waypoints".
First of all, we are setting up all the different variables for wp4, next you are storing all variables in a vector called "waypoint4". In the end, we store this vector in another vector named "waypoints".


So, the very first vector element would be waypoints[0] and it would point to another vector (waypoint4), the elements of waypoint4 would be also available by index:
So, the very first vector element would be waypoints[0] and it would point to another vector (waypoint4), the elements of waypoint4 would be also available by index:
Line 269: Line 269:
</syntaxhighlight>
</syntaxhighlight>


The only issue here is that you'll need to keep vector ordering in mind so that you can always access the right element number. This could be simplified by using some variables with telling names as the index for each vector:
The only issue here is that you'll need to keep vector ordering in mind, so that you can always access the right element number. This could be simplified by using some variables with telling names as the index for each vector:


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
Line 327: Line 327:
</syntaxhighlight>
</syntaxhighlight>


There's one caveat here: The set function doesn't check the size of the vector, so it may try to access elements that are not there. To fix this, you'd need to use size(vec) call and check its size, and then adjust the size as needed using the set size(vec, size) library function.
There's one caveat here: The set function doesn't check the size of the vector, so it's possible that it tries to access elements that are not there. To fix this, you'd need to use a size(vec) call and check its size, and then adjust the size as needed using the setsize(vec,size) library function.




Line 370: Line 370:




There's still one issue though: we are using lots of variables and helpers that all belong to the "waypoints" type, but which clutter our source code.  
Obviously, there's still one issue though: we are using lots of variables and helpers that all belong to the "waypoints" type, but which clutter our source code.  


So, next, we are going to look into an even more flexible approach that nicely maps each waypoint field and all helper functions to a symbolic name, without having to remember vector indices and such: using Nasal hashes, which are easier to use than complex arrangements using nested vectors.
So, next we are going to look into an even more flexible approach that nicely maps each waypoint field and all helper functions to a symbolic name, without having to remember vector indices and such: using Nasal hashes, which are easier to use than complex arrangements using nested vectors.


== A hash based version (recommended) ==
== A hash based version (recommended) ==
Line 416: Line 416:
* bearing
* bearing


Now, to access any of these fields, we would use the "dot notation" by first specifying the name of the enclosing context (which is just a fancy word for the name of the hash) and the name of the field we are interested in. You could read this as LOCATION.FIELD (i.e. get FIELD out of location).
Now, to access any of these fields, we would use the "dot notation" by first specifying the name of the enclosing context (which is really just a fancy word for the name of the hash) and the name of the field we are interested in. You could read this as: LOCATION.FIELD (i.e. get FIELD out of location).


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
Line 454: Line 454:
</syntaxhighlight>
</syntaxhighlight>


This is already a pretty cool thing because you would end up with 5 different containers all having their own set of fields, that you can access and set arbitrarily - without affecting the other containers.
This is already a pretty cool thing, because you would end up with 5 different containers all having their own set of fields, that you can access and set arbitrarily - without affecting the other containers.


But the cool thing comes next:
But the really cool thing comes next:


As you may have noticed, the code (internal structure of fields) for each waypoint is 100% identical - so we could just as well tell the Nasal engine to use an existing hash as a TEMPLATE for a new object instead of having to replicate the hash over and over again. This is accomplished using the "parents" keyword:
As you may have noticed, the code (internal structure of fields) for each waypoint is 100% identical - so we could just as well tell the Nasal engine to use an existing hash as a TEMPLATE for a new object instead of having to replicate the hash over and over again. This is accomplished using the "parents" keyword:
Line 467: Line 467:
</syntaxhighlight>
</syntaxhighlight>


This creates three different new containers/hashes by copying the fields from the hash specified in the parents' vector.
This creates three different new containers/hashes by copying the fields from the hash specified in the parents vector.


What parents will do is this: whenever a hash contains a vector field named "parents:" pointing to other hashes, it will look up the parent hashes and use them as a template and copy the fields of the parent hashes to the new hash, i.e. less typing for you!
What parents will do is this: whenever a hash contains a vector field named "parents:" pointing to other hashes, it will look up the parent hashes and use them as a template and copy the fields of the parent hashes to the new hash, i.e. less typing for you!
Line 531: Line 531:




Once we start using a hash as a template for other hashes using the "parents" vector, we are creating a class that is copied to each new hash. This new copy of the class is called an "object" after creation.
Once we start using a hash as a template for other hashes using the "parents" vector, we are actually creating a class that is copied to each new hash. This new copy of the class is called an "object" after creation.


A "class" is just a template for a certain data type that consists of other data types and provides functions to work with the class. The functions publicly accessible are called its "interface" because these functions are meant to be used by the users of your class. This is in contrast to member fields which are usually not meant to be accessed directly.
A "class" is really just a template for a certain data type that consists of other data types and provides functions to work with the class. The functions publicly accessible are called its "interface" because these functions are meant to be used by the users of your class. This is in contrast to member fields which are usually not meant to be accessed directly.


Once a class is used as a template to create a new object, we say the class is getting "instantiated", i.e. an instance of the class (an actual object) is created. This makes it then possible to make use of its interface and access member functions (which are methods). Also see {{forum link|p=145769}}.
Once a class is used as a template to create a new object, we say the class is getting "instantiated", i.e. an instance of the class (an actual object) is created. This makes it then possible to actually make use of its interface and access member functions (which are methods). Also see {{forum link|p=145769}}.




Now, given that the creation of new hashes using a template class is such a common thing to do - we could just as well add a new function to the parent hash that we can use to construct new hashes. As you could see already, the fields (or members) of a hash are specified in a well-defined form using the key/value format where key and value are separated by a colon: "field_name: value".  
Now, given that the creation of new hashes using a template class is such a common thing to do - we could just as well add a new function to the parent hash that we can use to construct new hashes. As you could see already, the fields (or members) of a hash are specified in a well-defined form using the key/value format where key and value are separated by a colon: "field_name:value".  


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
Line 590: Line 590:
</syntaxhighlight>
</syntaxhighlight>


On the other hand, we were just about to make the construction of such hashes much simpler. This can be done by creating a new function that constructs hashes dynamically using a hash:
On the other hand, we were just about to make construction of such hashes much simpler. This can be done by creating a new function that constructs hashes dynamically using a hash:


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
Line 626: Line 626:
== Hashes and foreach (enumerating a hash) ==
== Hashes and foreach (enumerating a hash) ==


To iterate through all elements in a "hash the keys(hash) method" is used to produce a vector of the keys for the hash.
To iterate through all elements in a hash the keys(hash) method is used to produce a vector of the keys for the hash.


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
Line 665: Line 665:
</syntaxhighlight>
</syntaxhighlight>


This will simply create a function named "new" which accepts an implicit list of arguments (in the implicit "args" vector). The function returns a hash whose parents field is set to the args vector, so this method could even be used for multiple inheritances. Note that the Position3D hash no longer has its constructor, it's just a simple hash in this case.  
This will simply create a function named "new" which accepts an implicit list of arguments (in the implicit "args" vector). The function returns a hash whose parents field is set to the args vector, so this method could even be used for multiple inheritance. Note that the Position3D hash no longer has its own constructor, it's really just a simple hash in this case.  


To make this look a little nicer, we can also rename the default argument vector (i.e. args) to something more informative, like "classes":
To make this look a little nicer, we can also rename the default argument vector (i.e. args) to something more informative, like "classes":
Line 687: Line 687:


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
# a new function with an implicit arg parameter that simply returns a new hash with the parents' field set to all arguments provided
# a new function with an implicit arg parameter that simply returns a new hash with the parents field set to all arguments provided
var new = func return { parents:arg };
var new = func return { parents:arg };


Line 696: Line 696:


Now, let's imagine you want to add another member function to print a certain field's value, such as "number".  
Now, let's imagine you want to add another member function to print a certain field's value, such as "number".  
Given that objects created from the same class template, there needs to be away for a method (class function) to refer to itself (its object), i.e. to refer to the object itself, otherwise, the function wouldn't know what object is meant, and what object it is referring to.
Given that objects created from the same class template, there needs to be a way for a method (class function) to refer to itself (its own object), i.e. to refer to the object itself, otherwise the function wouldn't know what object is meant, and what object it is referring to.


This can be accomplished using the "me" keyword which ensures that the member function is always referring to the currently active object (itself):
This can be accomplished using the "me" keyword which ensures that the member function is always referring to the currently active object (itself):
Line 718: Line 718:
</syntaxhighlight>
</syntaxhighlight>


Note how we are just prepending the "me" keyword to the object's field, making it clear that "me" is the requested namespace/context of the field to be retrieved.
Note how we are really just prepending the "me" keyword to the object's field, making it clear that "me" is the requested namespace/context of the field to be retrieved.


On the other hand, maybe you'd like to add some information (such as the number) during construction time to the object. So this would require changing the constructor function to accept a parameter, too:
On the other hand, maybe you'd like to add some information (such as the number) during construction time to the object. So this would require changing the constructor function to accept a parameter, too:
Line 833: Line 833:
</syntaxhighlight>
</syntaxhighlight>


As you may remember, the parents' keyword points not just to a single "class template" (hash), but instead to a vector of hashes. This makes it possible to use several different hashes as the template for a new hash:
As you may remember, the parents keyword points not just to a single "class template" (hash), but instead to a vector of hashes. This makes it possible to use several different hashes as the template for a new hash:


<syntaxhighlight lang="nasal">
<syntaxhighlight lang="nasal">
Line 851: Line 851:
var airplane = new(vehicle, propulsion);
var airplane = new(vehicle, propulsion);


# seaplane is a new type of vehicle that inherits from the classes boat and aeroplane
# seaplane is a new type of vehicle that inherits from the classes boat and airplane
var seaplane = new(boat, airplane);
var seaplane = new(boat, airplane);
</syntaxhighlight>
</syntaxhighlight>
Line 859: Line 859:


* a car "IS A" vehicle
* a car "IS A" vehicle
* an aeroplane "IS A" vehicle
* an airplane "IS A" vehicle
* an elephant "IS AN" animal
* an elephant "IS AN" animal


Line 903: Line 903:
</syntaxhighlight>
</syntaxhighlight>


This was a very simple introduction to object-oriented programming in Nasal.
This was a very simple introduction to object oriented programming in Nasal.


[[Category: Nasal howto|Module, Create a new Nasal]]
[[Category:Nasal howto|Module, Create a new Nasal]]

Navigation menu