|
By: Kevin Picone I N D E X: About The following was written to take the user through the basic usage and concepts associated with Types. The article mostly follows in a top down nature. With the most advanced concepts located towards the bottom. There's key headings all the way through however, so if your already familiar with types you should be able to skim through the headings to brush up on PlayBASIC's implementation. Have you read the Variables & ArrayBasics tutorials ? No - might be better starting there then. Type's, What are they? User Defined Types allow users to create our own data types, and are the natural extension of the simpler data types your no doubt already familiar with, like Integers, Floats and Strings. The main difference, is that Types allow us to bind a series of simple data types into a collection. Ideally so we can group together sets of properties that are in some way related to each other. Which is a good programming practice to get into. Declaration of a User Defined Type Lets imagine we wish to store a collection of information about a person. The collected information might cover the persons first, middle, surname (family) name, home address, phone number, type of employment etc etc. While we could store all those pieces of information separately in traditional variables or arrays, a better approach would be to create a User Defined Type (UDT) to house all of individual properties together. So we can manage the group of information tidbits together. I.e., In this example we're declaring a type called "Person". This type will hold all of the relevant information about a single person in one place.
The code above purely shows the declaration of our "Person" type. We can see the type has seven string fields FirstName$,MiddleName$,SurName$,HomeAddress$,WorkAddress$ and PhoneNumber$ are contained within this type. At this point we can't really do much with it, we need to create a variable or array of this type, in order store and retrieve information about our actual person. Declaring a Typed Variable Once a UDT (user defined type) has been declared to PlayBasic, we can then create our own variables and even arrays of that type. To create a typed variable we use the "Dim" command, followed by our Variable Name then the "as type" to specify what data type this variable should use. This will create our variable ready for us to store information in it.
Declaring a Typed Array Once a user defined type has been declared to PlayBasic we can then create array's to store collections of individual instances in. Just like regular integer/float & string arrays. To create a typed array, we use the Dim command, followed by our array name, the size and finally "as type" to specify what data type this array should use. This will create our array ready for us to store information in it.
Initializing Individual Type Variable & Array Elements (the NEW operator) When we deal with UDT (User Defined types) be that in Variable or Array form. The dimensioning process declares the Variable or Array to PB as a particular data type. Now during this process, PB builds a container that will hold our Type(s) data in. But it doesn't actually allocate the type memory (a place to store our type fields in) themselves. So we can do that two ways.
While both are acceptable, the latter option just gives you explicit control over what type your allocating. Example of explicit type allocation
Storing Information In Type Variables Once we've declared a User defined type and created a variable to hold and retrieve data, we can now happily start filling this variable with information. This is probably the biggest difference you'll notice to using regular variables. That is when we store information in a type variable/array we need to not only specify the variable name, but the field name also. As remember this variable doesn't just contain one piece information anymore (like a normal integer/float or string variable would) but rather, it now houses all the fields of your "person" type. So we have to clearly specify what field we wish to store our information in. Note: To separate the Variable and Field names PlayBasic uses the "." character convention.
Retrieving Information From a Type Variables Retrieving information from a type variable or array is the same storing information in it, we simply enter our type name with field in our expression, and PlayBASIC will retrieve this information for you.
What Kind of Fields can User Defined Type Contain ? At the time of writing (PlayBASIC Version 1.64k) user defined type fields are limited to the following.
Note: We're planning on expanding the field support in the future. So when in doubt, try them ! What are STATIC ARRAY FIELDS ? Array Fields within types can often be a source of confusion for new programmers, which is no great surprise given the amount of terminology being thrown around. If you've been read through the ArrayBasics tutorial or had a mess about with the DIM command, then you're likely to assume that array fields within a type, are the same as those others. But they're actually not. On the surface, static array fields and regular dim'd arrays might seem to be the same concept, until you peel away to see how they're implemented. When we declare an array field within a type, PlayBASIC builds the type structure (during compilation) with a predefined chunk of memory at this position within the type. So if we had a type with a couple of integer fields and a static 1D integer array field like the example bellow, the compiler builds this structure up in memory before the program ever runs. Since it's built at compile time, the type structure can't be modified while our program is running. So the Values field will be permanently constructed to hold 5 (0 to 4) separate integers in the TestType structure.
Peeling deeper inside this type declaration and we could actually think of our Values fields, not as an array but rather a collection of individual fields. In the following example, we've created a compatible data structure, even though it doesn't use an array at all.
How is this the same ? -- Well, they both define two ways of looking at the the same structure of memory. If we allocated and filled in a type of TestType, we could actually read it using TestType_UNrolled . Like so,
This works because the two patterns of memory are formatted identically, using the array version is just easier for the programmer, since they can loop through the individual cells. What is demonstrates though is the array firstly embedded in the data and that's there structural header information about the array stored in within the type. So far, it looks like a regular array on the surface, but it's here the similarity ends. With a regular array, we can use the array commands to do all kinds of handy things from managing it's size (DIM / REDIM), bounds protection, searching, copying, recreating , pass arrays into / out of functions etc etc We can do this because PlayBASIC avoids referring to the arrays memory directly. Rather it uses a special high level HANDLE interface (unique numeric ID's) when querying, passing or changing the information (the raw memory) inside of it. This helps create a distance between your program code and the raw array data bellow it. Static Array Fields in user defined types are as the name suggests, static and don't have high level handle interface of dynamic arrays, which is demonstrated ion the previous example So when we set or get information held in an array field, we're reading/writing directly to that particular location in memory inside of the type structure. The static array is a pre-computed area within a type structure, so we can't change it's size, or apply any of the array command set functionality to it. Now since dynamic and static arrays appear similar on the surface, but are radically different internally, it's not uncommon for new PlayBASIC programmers to try and mix and match them with the arrays commands. The Static array doesn't have a high level Handle interface, so that just won't work. For example,
This works because we're passing the GetArrayElements() the handle of the dynamic array Player(), but what if we try and query the number of elements in the Inventory$() field with one of the players ?
This won't even compile, let alone work. This is because the statement Player(1).Inventory() isn't returning a handle to the Inventory field array, it can't, static arrays don't use them, rather it's returning the physical pointer (Address) of the first field of the inventory within the structure. You can demonstrate this yourself with the following,
Nesting Types (Declaring Typed fields) In our previous examples we've been building a type to house all the fields relating to our person. While perfectly workable at the moment, more information we store in our person, the more rigid things become dealing with them. So as types get bigger (store more and more information) it's often easier to break them down into smaller and more focused types. Lets look at this from our of Person type example. Currently our person contain fields, but what if we wanted to store more robust information relating this Persons Home and Work details. For example stuff like Address, Road, State, ZipCode, Country, Phone Number. While we could use something like the following, it's not the most elegant solution.
As you can see, in this example we have two groups of fields storing information relating the Home and Work addresses of this person. Notice how these groups all share the same basic fields. The only distinguishing difference is the "Work_" or "Home_" prefix , that has been built into each individual field. While this is a usable approach, the main issue with it, is that we can't deal with our sub groups of Work or Home fields separately. This presents us the perfect opportunity to create a second smaller type called "HomeInfo". This type will hold all the fields that a house address requires. We'll then use this type to create a two nested type fields HOME and WORK. Both of fields with be of type HomeInfo inside our person. I.e
The first thing you'll notice is that by creating the HomeInfo type and nesting it within our Person type as Work & Home, we've actually reduced the declaration code somewhat. That's not the only advantage though, so here's some others are probably less obvious. Advantages of separating the 'HomeInfo"
Note: Make sure you examine the Type Tutorial examples. These can be found in the \Projects\Tutorials\Types. Making Assignments Between types (Variables & Arrays) Type assignments allow us to copy all the fields of a types from one place to another. The syntax follows the same format as a regular variable assignment, just that we have to explicitly name the type fields we wish to copy to/from. So we are copying between two typed variables the syntax should look like this I.e ( DestVariable.ThisType = SourceVariable.ThisType) During the COPY, PB will allocate the destination type automatically if doesn't already exist. So after copying, we'll have a new separate clone of the original. It's important to explicitly declare the field you wish to copy, otherwise PB will assume you want to read/write between type elements. (ie. DestVariable=SourceVariable) The problem with this, which is perfectly legal coding practice, is that your moving the types Handle from the source variable into the destination variable, rather than copying it. So now both variables would contain the same handle. So their both pointing to the same data in memory. Therefore, if you change one, the other will change. You can copy between any pair of types you like, be they typed variables, arrays, a combination of variables/arrays, nested within other types, or even in the same type if you like. So it's pretty flexible in that regard. The basic of which are demonstrated in the following example.
Type Inheritance (Create New Types from Existing Types ) Inheritance is another one of those concepts taken from the world of Object Oriented programming. The concept is simple, but it's importance is often overlooked, in particular in regards to maintaining your programs structure and expandability. Conceptually Type inheritance allows us to create new types (children) from existing types (the parent). These new child types inherit all the fields of the parent. From here you can then add extra unique fields to the child type. This allows us to create chains (hierarchies) of descendant types. In this example we'll rework our running "Person" example. But this time we'll rename Person as BasePerson type. This new type will be the parent from which all child types are derived. The parent will only contain those fields that are common to all people. Fields like Name, Age, Height ,Weight and hair colour for example. From here, we'll use inheritance to spawn a MALE and FEMALE child types. Adding any unique fields to these new types.
In this example we're created two different child types from the same parent. It's important to understand that both the MALE and FEMALE types would now contain all of the base Parent fields as well as their uniquely declared fields. So if we were to examine the fields inside MALE & FEMALE types it's the equivalent of the following.
While we could happily create our types individually like the second example. Inheritance has few benefits that you should at least familiarize yourself with. But to be honest it often depends upon the size of the project to which approach you'll choose to use. Benefits cans be,
Passing Types To Functions (Variables + Arrays ) Types and Functions go hand in hand. While types allow us to group data together, functions allow us to roll pieces of code that can perform a particular calculation or operation upon a data set. Now hopefully you've already familiar with functions & Psubs (if not, be sure to brush over the Tutorial on them). So lets get the nut and bolts about how we can pass Typed Variables/Arrays and pointers into functions. This really one of the more advance subjects relating to Types in PlayBasic. Failure to get your head around these different approaches will no doubt see your programs suffer. Don't be afraid though, their bark is nastier than their bite and once you come to grips with them, you'll have given yourself the best opportunity to develop cleaner, faster, and more easily expandable programs! In PlayBasic we have three basic controls over dealing with typed data (Arrays/Variables) in general, those being "Handle", "Element" and "Pointer". Handles() - When we talk about handles, we are referring to the entire variable/array container, and not the individual items this container may house. To pass by handle we use the closed bracket symbol at the end of our variable/array name. Ie. MyArrayHandle() ie
Element - When we address a typed array/variable by element, were reading/writing into the array/variable container and grabbing the handle of an individual type. The Handle of the type is much like a Where not addressing the data fields within a particular type though, but the handle of containing structure that holds all of this types fields.
You can query the type of any returned element, using the "TypeOF()" function. This Function returns the Type INDEX of the queried element. Both type variable & arrays can house not only types of their parent type declaration, but child types (types inherited from the parent) also. So you could store everything in the one array or variable if you like ! I.e.
Pointer Addressing - Pointers give the address in memory of a particular type instance. So by pointer uses the address of this type instance (the first field in type), and not the structure if came from. You can get a pointer to any element by adding the ."TypeName" after addressing the element. You can also get the pointer to any nested types. This allows us to copy type structures between instances directly. Moreover you can write totally generic functions that accept/return typed pointers. ie.
|
Related Info: | ArrayBasics | Dim | FreeCell | Functions&Psub | GetFreeCell | Loops | New | Null | Pointer | Type | Variables : |
|
|||||||||||||||||||||||||||||||||||||||
(c) Copyright 2002 - 2024 - Kevin Picone - PlayBASIC.com |