BY MATT NEUBURG
Originally published in: MAC DEVELOPER JOURNAL Spring 2004
The basic in REALbasic's name is somewhat misleading. REALbasic is a modern object-oriented garbage-collected language. It rivals respected languages such as Java in elegance and power -- particularly in light of its many changes since the publication of the second edition of my book, REALbasic: The Definitive Guide (O'Reilly, 2001). That book documented REALbasic 3.5; now, two years later, version 5.5 is in beta. [Actually, now it's four years later, version 5.5 is current, and version 6 is in beta.] This article describes some of the changes to REALbasic. Note that I'm talking here just about the language. Naturally, the development environment, the repertory of built-in classes and interface widgets, and so forth have also undergone many improvements.
All REALbasic code resides in a method of some class, or of a global entity called a module. A class or module can also define properties (instance variables) and constants. The ability to define constants in a class is new; previously, you had to define constants globally in a module or locally in a method.
In code, you call a method, or refer to a property, by means of dot notation appended to a reference to an instance of that class. Here, MyClass
has a string property, itsString
, and a method, itsStringMaker
, that returns a string:
dim c as MyClass c = new MyClass c.itsString = c.itsStringMaker()
References to instances are pointers, but REALbasic hides this fact from the programmer. Garbage collection is by means of reference counting; the object to which the automatic variable c
in the above code points, if there are no other references to it, is destroyed spontaneously when c
goes out of scope.
A class is a subclass of some other class (with object
as the ultimate superclass), and inherits its superclass's methods and properties. A subclass may add properties but cannot refuse an inherited property. Any inherited methods may be overridden, with polymorphism implemented in the usual way (the search for a method starts with the class of the instance to which a message was originally sent).
There are also class interfaces, which are essentially collections of method declarations. A class that implements a class interface must implement all of its methods. A class may implement any number of class interfaces.
Two methods of the same class may have the same name, provided the number or types of their parameters differ; this is called overloading.
A class may have a constructor and/or a destructor. The constructor may declare parameters; in that case, the new
call will supply arguments, as shown here:
// MyClass: Sub Constructor() self.itsString = "howdy" End Sub Sub Constructor(s as string) self.itsString = s End Sub Sub Constructor(c as MyClass) self.itsString = c.itsString End Sub // elsewhere: dim c as new MyClass dim c2 as new MyClass("hello") dim c3 as new MyClass(c2) c2.itsString = "bonjour" msgbox c.itsString // howdy msgbox c2.itsString // bonjour msgbox c3.itsString // hello
That code also illustrates two features that have arrived since the publication of my book. Constructors can now be named Constructor
instead of having the same name as the class (convenient if you change a class's name). And the first three lines of the elsewhere
code show declaration and initialization in the same line.
Previously, a method's parameters could not be optional. You could work around this limitation in various ways -- through overloading, for instance, or by passing a collection or dictionary object. Now a method can make parameters optional by declaring default values for them; the supplied arguments are paired with the declared parameters in left-to-right order:
Sub myMethod(s1 as string = "hey", s2 as string, s3 as string = "ho") msgbox s1 + s2 + s3 End Sub // elsewhere in the same class: self.myMethod("ha") // heyhaho self.myMethod("ha", "hi") // hahiho
Also, a ParamArray
parameter mops up subsequent arguments into an array:
Sub myMethod2(paramArray args as variant) dim arg as string dim argStrings() as string for each arg in args argStrings.append arg // make string array for join() next msgbox join(argStrings) End Sub // elsewhere in the same class: self.myMethod2("hey", 1, "ho") // hey 1 ho
The preceding example takes advantage of the fact that a variant can be anything, even a scalar, and that it is coerced automatically on assignment. The for..each
construct has also been added since the release of my book (as has the join
function). Unfortunately, for..each
works by assignment, not by aliasing, so you can't use it to alter scalar items of an array in place, as in Perl.
In an earlier example, I said:
c.itsString = c.itsStringMaker()
What if you don't want just any old code to be able to access MyClass
's itsString
property this way? In the past, you could mark a member of a class as private, which actually meant what most folks would call protected; code in the same class or its subclasses could access a private member. This terminology is now rationalized; a member of a class can be public, protected, or private (where private now means that only the defining class can access it).
A member of a module can also have any of three privacy levels. A private member can be accessed only by code within the module. A public member and a global member differ in that the public member, though available everywhere, requires use of the module's name; this is much nicer than the rather messy global namespace that comprised all modules in earlier versions of REALbasic.
Now comes the cool part: getter and setter methods can give the illusion of accessing a property. For example, suppose MyClass
has no itsString
property, but rather has a protected property, itsProtectedString
. Then I can define public MyClass
methods as follows:
// MyClass: Sub itsString(assigns s as string) self.itsProtectedString = s End Sub Function itsString() As string return self.itsProtectedString End Function
The effect is that my earlier code accessing a MyClass
property, itsString
, still works. I can still say this, for example:
dim c1 as new MyClass("hey") dim c2 as new MyClass("ho") c1.itsString = c2.itsString
This syntax gives the caller the illusion of getting and setting a property when in fact it's calling methods. Of course, you could do much more in these methods than get and set a property.
Another new linguistic elegance is the ability to define what should happen when a value of one data type appears where a different data type is expected. You do this through operator_convert
methods. A class can define a from converter and/or a to converter. You can implement multiple operator_convert
methods for a class, describing what to do for different data types.
For example, suppose I define MyClass
methods as follows:
Function operator_convert() As string return self.itsProtectedString End Function Sub operator_convert(s as string) self.itsProtectedString = s End Sub
Now I can use a MyClass
instance where a string is expected, and vice versa; the value will be coerced implicitly, according to the methods I've defined. So I could rewrite an earlier example thus:
dim c as new MyClass dim c2 as new MyClass("hello") dim c3 as new MyClass(c2) c2 = "bonjour" msgbox c // howdy msgbox c2 // bonjour msgbox c3 // hello
Actually, I can go even further, replacing the second line with this:
dim c2 as MyClass = "hello"
Refer back to my earlier myMethod2
example; this will now work:
self.myMethod2(new MyClass("hey"), new MyClass("ho"))
That's because the assignment from a variant to a string in myMethod2
now knows what to do when the variant holds a MyClass
instance.
You can define arithmetic operators and comparison operators for classes. Each such operator has two possible definitions, depending on which side of the operator self
appears on. So, for example, I might implement addition for two MyClass
instances and for a MyClass
and a string:
Function operator_add(s as string) As MyClass return new MyClass(self.itsProtectedString + s) End Function Function operator_add(c as MyClass) As MyClass return new MyClass(self.itsProtectedString + c.itsProtectedString) End Function Function operator_addRight(s as string) As MyClass return new MyClass(s + self.itsProtectedString) End Function // elsewhere: dim c1 as new MyClass("ho") dim c2 as new MyClass("ha") dim s as string = "hi" msgbox c1 + c2 // hoha msgbox c2 + c1 // haho msgbox s + c1 // hiho msgbox c1 + s // hohi
In the past, equality comparison of instances reported the identity of the instances (the two references point to the same thing). If you define comparison operators, that won't work, so a new operator, is
, reports the identity of the instances:
Function operator_compare(c as MyClass) As integer return strcomp(self.itsProtectedString, c.itsProtectedString, 1) End Function // elsewhere: dim c1 as new MyClass("hi") dim c2 as new MyClass("hi") if c1 = c2 then msgbox "they are equal" if not (c1 is c2) then msgbox "they are different instances"
(The single-line if..then
construct is new as well.)
You may define a method on a class from outside that class. The point of this feature is to add methods to a class belonging to REALbasic rather than to the programmer; this is similar to Objective-C's categories, and works even for scalar types. The method needs to be global (that is, defined in a module). For example, here's a workaround for REALbasic's lack of an arithmetic assignment operator:
Sub inc(byref extends i as integer, j as integer = 1) i = i + j End Sub // elsewhere: dim i as integer msgbox str(i) // 0 i.inc msgbox str(i) // 1 i.inc(10) msgbox str(i) // 11
REALbasic itself takes advantage of this feature to reduce the prevalence of global functions. For example, you can now say s.uppercase
instead of the inelegant uppercase(s)
. Curiously, the migration away from global functions has not been thoroughly carried out; you must still use str(sqrt(i))
-- to say i.sqrt.str
is illegal. Of course, you can make it legal by implementing sqrt
and str
as class extensions.
In the past, you could handle exceptions only at the method level. A method could end with an exception handler, which would catch exceptions raised within the method. If you wanted to apply exception handling to a smaller region of code, you had to factor out that code into a separate method, which was often inconvenient or not even feasible. Now, there's a try..catch..end
block structure (with an optional finally
block), similar to the one in Java.
Return types are now covariant. This means that given a MyClass
class and its MySubclass
subclass, if you define myMethod
somewhere as returning a MyClass
, it's OK to override myMethod
with a method defined as returning a MySubclass
. (The lack of this feature is the keystone of a forceful critique of Java by Dragos Manolescu and Adrian Kunzle; see this pdf.)
Arrays in REALbasic have always suffered from an identity crisis: they are not mere scalars, but they aren't full-fledged objects either. Consequently, arrays have been subject to many special restrictions. Arrays are still neither fish nor fowl, but the latest version of REALbasic has largely lifted or rationalized these restrictions; for example, you can now assign an array to a variable or return it from a function, and you can pass multidimensional arrays as parameters.
This version of REALbasic removes a number of small annoyances. For example, you can now wrap long lines using a line-continuation character; the variable in a for
loop doesn't have to be an integer; in a select
block, case
statements have a much more flexible syntax; and dim
statements (local variable declarations) no longer have to precede all other statements in a method.
The REALbasic language does have its failings. Some of these are minor inconveniences and inelegancies. I sometimes wish for REALPerl, REALJavaScript, or whatever, if only to have a unary increment operator, arithmetic assignment operators, and so forth. But such dissatisfaction occurs with any language.
The one major linguistic feature whose lack I feel keenly is class methods. You cannot, for example, call a MyClass
method without first generating a MyClass
instance. This makes it impossible to implement Singleton properly -- or rather you can implement it, but you can't enforce it, since no one can create an instance unless its constructor is public, in which case anyone can create one. An easy workaround involves instantiating all Singletons at startup; you can then enforce the singularity by raising an exception in the constructor. But this isn't really a true Singleton.
The pertinent question, however, is whether REALbasic is adequate for serious programming, which means object-oriented programming, since REALbasic is an object-oriented language. The answer, on the whole, is clearly yes. REALbasic is a good cross-platform application framework. But even more crucial, it uses a true object-oriented language, in which you can fully implement most of the important design patterns, possessing some powerful elegances of expression.