Types & fields

BlitzPlus Forums/BlitzPlus Beginners Area/Types & fields

Linus(Posted 2012) [#1]
can someone teach me types and fields because i have no idea.
i´m happy for every answer.


Yasha(Posted 2012) [#2]
We can try...

OK. A type is two related things in Blitz Basic:

1) it is a property of a variable, describing what that variable is allowed to hold:

Local a%, b     ;can hold integers
Local c#       ;can hold floats
Local d$       ;can hold strings


2) it is also a property of data, describing what "shape" the data has in memory (e.g. an integer is four bytes that represent base-255 digits; a float "is" a sign-bit + exponent + mantissa; a string is a lot of text characters stuck next to one another).

In Blitz Basic these two things map 1:1 - a variable may only hold data that matches the type of data it's been tagged as allowed to hold.

Easy so far?


While ints, floats and strings are lovely, and technically enough to do pretty much anything if we're patient, when we want to represent more complicated things, it's nice to be able to deal with the data representing those things in a more intuitive way. Thus, Blitz also permits us to define new types of our own, by building aggregates of the three "primitive" types (and simpler types of our own definition as well). For instance:

Type Position    ;An XY position in screen space
    Field x, y
End Type

Local pos.Position


...describes a very simple data structure: a 2D position on the screen. The Type/End Type block describes the structure of the memory (two integers), and the Local declaration shows how you tag a variable to hold a value of this particular type, instead of a primitive like the ones above.

Since a Position is just a block of memory, we need to be able to describe how to perform operations on it in a way Blitz understands. Blitz only "knows" how to do anything useful (like adding numbers, printing text) with the builtin three data types, so the field declarations describe to Blitz how its block of memory is structured and how to access "bits" of it in terms of types it actually understands. In this case, we've told it that a Position is equivalent to two integers. That means when we have an object of type Position, we'll be able to access each of its components using the field names, and read them/compare them/draw with them/do math with them. Fields are accessed with the \ operator: so to read the x field of the Position held in pos, we would say "pos\x". You can treat this whole construct like a variable or array-slot, and assign to it ("pos\x = 10"), or pass it to functions ("Print(pos\x)"), or whatever.

There is however one catch to working with user-defined types. The primitive three are all "value types", in that 1) there's always something there, and 2) they don't have any sub-components, so you can't change the "nature" of a number (if you add to it, you get a different number back, that's how maths works).

User-defined types, on the other hand, are reference types. Firstly, this means they don't just exist automatically like e.g. numbers (since numbers are a theoretical construct, obviously you never have to "create" one), but must be created with the "New" operator; and secondly, it means that if we do this:

Local x = 42, y = x    ;y holds the same number as x
y = 47    ;You can't modify a number, so x is unaffected here

Local pos1.Position = New Position    ;pos1 holds a freshly created Position
pos1\x = 42 : pos1\y = 47    ;We can set its components

Local pos2 = pos1    ;pos2 holds THE SAME Position!
pos2\x = 64    ;...and is able to modify the same fields visible through pos1

Print pos1\x
Print pos2\x


This is because the variables tagged as Positions don't actually "hold" the memory block for the Position directly (this would be no problem for a two-int XY location, but imagine the slowdown if we held an image and then assigned it to another variable and everything had to be copied!). Instead, they hold a reference to the actual Position object, which has been created "off to one side" from the program's main stack.

So, each time we want a completely new value of type Position, we must create a new block of memory with New: this block is called an object. We can then pass around a reference to this object by assigning it to variables or passing it to functions, but no matter how many references there are (say we did "Local pos3.Position = pos2"), this by itself has no effect on the number of objects. If two variables hold references to the same object, they can read and modify the same set of fields. (This is actually pretty useful sometimes!)

Because we had to manually create the object with New, it lives "off to the side" of the main program stack, and unlike simple variables in a function call frame, will still exist even if no variables actually hold a reference to it. So when we're done with an object, we must delete it with the Delete operator:

Delete pos2    ;pos1, pos2 (and pos3 if you declared it) are all now Null: trying to read their fields will crash the program!


All Delete does is destroy the block of memory. If you had an object that contained e.g. a picture, that picture would be "leaked" because Delete doesn't know how to call FreeImage: so you might need to write a "destructor" function that takes care of tidying up after complicated objects.

(As an aside: images are a great way to understand by-reference semantics: the image exists in graphics memory, not in your program; and two int handles can refer to the same image at once; it is created by a special call and destroyed by a special call, rather than having its life tied to a variable.)

Finally, a field is like a variable in every way, except that it exists inside an object's memory block. So it too can contain images (as mentioned above), or it can contain references to other objects, declared in the same way as a simple variable would be:

Type NamedPosition
    Field name$
    Field pos.Position
End Type

Function MakeNamedPosition.NamedPosition(x, y, name$)
    Local np.NamedPosition = New NamedPosition
    np\pos = New Position
    np\pos\x = x
    np\pos\y = y
    np\name = name
    Return np
End Function

Function FreeNamedPosition(np.NamedPosition)
    Delete np\pos
    Delete np
End Function


Just as an object isn't cleared up from a normal variable automatically, or put in one automatically, so too the "pos" field of NamedPosition needs to be filled by hand, and cleaned up by hand before we take care of the NamedPosition itself. "pos" doesn't "belong" to "np" in any special way (except in our design pattern), so it's only held within "np" by-reference: if we did this:

Local named.NamedPosition = MakeNamedPosition(0, 1, "foo")
Local pos4.Position = named\pos


...we'd be able to modify named\pos by modifying pos, because they are currently references to the same object.

You can see above how to pass in and return references to and from functions (just tag the parameter appropriately); you can also put them in arrays:

Dim posList.Position(100)    ;Dim-style array
Local posBlock.Position[100]    ;C-style array


(Look up "Blitz arrays" to learn more if you don't yet know the difference between the two types of array - it's not important to this though.)

So, you can move references around like any primitive Blitz type, and the references give you potentially-distributed control of your objects held "outside" the program logic, much like images, sounds, or 3D entities. Don't lose them!


Where is appropriate to use custom types?

1) Anywhere you want a group of properties to represent one "thing" you want to think about as a single "thing". A position, for instance, is best thought of as a discrete thing on its own; that way, if you need an array of positions, you can fill an array with them, and not have to have two arrays holding each dimension separately (what happens when you switch to 3D? Adding z to Position is easy; adding another array all through your movement code is really hard!).

2) Anywhere you want to lock variables into holding a particular category of value. Say you have a program which deals in both weights and counts of items; you could create a Weight type and a Count type, and because of the strong-typing rules about variable tags, be sure that you never inadvertently assign a Weight value to a Count variable.

3) Anywhere you want to share access to the same set of values from two code locations. Two separate pieces of code could manipulate the same integer slot without necessarily having to know about each other, by creating one object and having a reference to it in two places.

4) Anywhere you need to bind together multiple values to move in one go. If you wanted to return multiple numbers from a function (say you wrote a function DivMod, that computes both the remainder and the quotient after a division - two separate numbers), you could put them all into one object and return that.


Once you learn how to use custom types, you'll wonder how you ever did without!



There are also at least two "advanced" topics related to Blitz types, which I will not describe right now because you need to fully understand the above first. They are:

-- the built-in typelists (the use of the Before/After/Insert/First/Last/Each commands). These have absolutely nothing to do with the fundamentals of types and objects! I strongly recommend you completely ignore all mention of these until you are very happy using objects on their own. Most tutorials confuse the two, and this mess is one of the reason custom types got their unfair reputation for being "hard". Lists are easy once you understand the type system proper; leave it until then.

-- The Object/Handle commands. These allow you to do "advanced" things with objects. Again, ignore these for now until you're really happy with by-reference semantics, because they add another layer of complexity over the top.